likes
comments
collection
share

axios 源码分析 - 学习utils中的方法(merge/forEach/extend)

作者站长头像
站长
· 阅读数 12

前言

axios是前端主流的http库,基于promise,可以用在浏览器node.js中使用,为了提高效率,axios使用了一些工具库(utils),这是一个非axios特有的通用帮助函数库,本文对这个文件的重点函数(merge/forEach/extend)进行解析,可以让我们更好的理解axios源码和学习。

utils.js

function isStandardBrowserEnv() {
  var product;
  if (typeof navigator !== 'undefined' && (
    (product = navigator.product) === 'ReactNative' ||
    product === 'NativeScript' ||
    product === 'NS')
  ) {
    return false;
  }

  return typeof window !== 'undefined' && typeof document !== 'undefined';
}
  • 此方法来判断是否标准浏览器环境
  • 先把navigator.product赋值给了product变量,如果是ReactNative说明是react-native,如果是NativeScript或者NS,说明是nativescript
  • 浏览器环境windowdocument都不为undefined
  • 可以看出此方法很全面,考虑到了很多情况,我们也可以根据此方法进行改造,比如判断react-native环境等。

function forEach(obj, fn) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}
  • 正常的forEach方法都是遍历数组的,这里的forEach允许我们遍历一个对象
  • 如果obj参数是null,或者undefined,我们不调用fn方法,直接返回
  • 如果使用typeof是非object类型,则使用数组包装一下
  • 接下来可以看到,如果是数组,则用fn.call调用,这部分和Array.prototype.forEach源码相似
  • 如果是非数组,使用for ... in循环遍历obj,注意这里遍历是会把非自身属性(原型属性)也执行的,所以我们需要用Object.prototype.hasOwnProperty.call(obj, key)来判断,必须是自有属性才调用fn

function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (isPlainObject(result[key]) && isPlainObject(val)) {
      result[key] = merge(result[key], val);
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
    } else if (isArray(val)) {
      result[key] = val.slice();
    } else {
      result[key] = val;
    }
  }

  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}
  • merge方法,支持传入无限个对象,最后合并成一个新的对象返回
  • 先定义了一个变量result = {}
  • 然后可以从下面开始看,遍历了arguments,然后使用刚才的forEach遍历对象方法,让每一个obj都执行assignValue这个函数
  • assignValue,接受第一个参数是key,第二个参数是key,然后函数做了几个事情:
    1. 如果key已经存在result中且是普通对象,并且val是也是普通对象,则递归调用merge方法,让result[key]等于合并2个对象之后的值
    2. 如果val是普通对象,则递归调用merge,传入{}val合并后的新对象,返回给result[key]
    3. 如果val是数组,则把当前val拷贝一下赋值给result[key]
    4. 其他情况,正常赋值
  • 如果多个对象是相同的key,则以最新的key的值为主。
  • 如果val是数组,则拷贝新的值。
  • 比较复杂的点在第一步,如果key已经在result中存在,我们会判断新老值是不是都是普通对象,如果都是普通对象则递归调用并更新老的值,如果老的值非对象,也会被替换

function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}
  • 继承方法,接收三个参数a,b,thisArg,目的是把b的所以属性和方法绑定到a上,并且改变this指向
  • 遍历b属性, 判断如果thisArg存在,且b的参数为function类型,则我们把这个方法绑定给a,并且它的指向为thisArg
  • 其他情况返回val,并赋值给a
  • 可以看到extend方法还是比较简单的,做好function非function赋值即可,还要注意thisArg的指向问题。

结语

本文了解了如何通过navigator判断是否浏览器环境,并且会举一反三给出判断其他环境的方法。讲了如何遍历对象的forEach方法,了解到了使用for...in + Object.prototype.hasOwnProperty获取对象的自有属性。学到了如何用forEach与递归结合让多个对象合并成一个对象的merge方法。