Axios-1-MergeConfig

408 阅读3分钟

axios 第一步 MergeConfig

axios 源码解析 源码地址 https://github.com/axios/axios

1. 简单使用

   1.  axios.get("xxx", function(req,res) => {  }  )
   2.  axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred'} });

2. 源码初始

在 源码 dist/axios 文件 当你调用 axios.get()的时候

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
    Axios.prototype[method] = function(url, config) {
      return this.request(mergeConfig(config || {}, {
        method: method,
        url: url,
        data: (config || {}).data
      }));
    };
  });

当你使用 post 方法 时,如果 是 postForm 方法,最终还是调用了 this.request 和 mergeConfig 方法

 utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
 
    function generateHTTPMethod(isForm) {
    // 使用高阶函数,存储 isForm 
      return function httpMethod(url, data, config) {
      
        return this.request(mergeConfig(config || {}, {
          method: method,
          headers: isForm ? {
            'Content-Type': 'multipart/form-data'
          } : {},
          url: url,
          data: data
        }));
      };
    }

// 等于 Axios.prototype[method] = httpMethod
    Axios.prototype[method] = generateHTTPMethod();
    Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
  });

从 里往外扒开 代码,先从 mergeConfig 开始

源码初始-mergeConfig

mergeConfig 顾名思义,就是为了 合并 默认的 config 与 用户传进来的 配置

axios.get("xxx", function(req,res) => {  }  )
axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred'} });


mergeConfig(config || {}, {
        method: method,
        url: url,
        data: (config || {}).data
  })

先以 axios.get 为例,看 mergeConfig 的处理方法

// 可以看出 config1 是用户的配置,config2 是默认配置

var mergeConfig = function mergeConfig(config1, config2) {
    
    config2 = config2 || {};
    var config = {};


utils.forEach(
    Object.keys(config1).concat(Object.keys(config2)
  ), function computeConfigValue(prop) {
  //  prop = config1 和 config2 的 key 值
  
  // 调用  mergeMap[prop] || mergeDeepProperties 返回一个函数
      var merge = mergeMap[prop] || mergeDeepProperties;
      
      var configValue = merge(prop);
      (utils.isUndefined(configValue) && merge !== mergeDirectKeys)
      || (config[prop] = configValue);
    });

    return config;
}

// 先看 mergeMap 就是一个 映射表 
var mergeMap = { 
   'url': valueFromConfig2,
    'method': valueFromConfig2,
    'data': valueFromConfig2,
    'timeout': defaultToConfig2,
     'timeoutMessage': defaultToConfig2,
    'baseURL': defaultToConfig2,
    'cancelToken': defaultToConfig2,
    ...
}
url, method, data 是 valueFromConfig2
baseURL,timeout,cancelToken 是 defaultToConfig2,

mergeConfig-defaultToConfig2

也就是简单的合并 value 值

 function defaultToConfig2(prop) {
      if (!utils.isUndefined(config2[prop])) {
        return getMergedValue(undefined, config2[prop]);
      } else if (!utils.isUndefined(config1[prop])) {
        return getMergedValue(undefined, config1[prop]);
      }
    }
mergeConfig-valueFromConfig2

如果 config2 -{url,method,data} 中有这个 prop ,执行 getMergedValue

function valueFromConfig2(prop) {
      if (!utils.isUndefined(config2[prop])) {
        return getMergedValue(undefined, config2[prop]);
      }
  }
  getMergedValue
mergeConfig-mergeDeepProperties
// 比较简单,返回的是合并的 value 值
  function mergeDeepProperties(prop) {
      if (!utils.isUndefined(config2[prop])) {
        return getMergedValue(config1[prop], config2[prop]);
      } else if (!utils.isUndefined(config1[prop])) {
        return getMergedValue(undefined, config1[prop]);
      }
    }
mergeConfig-getMergedValue
// 如果 target 和 source 是  普通的对象{},执行合并
// 如果 source 是 对象,返回 source
// 如果 source 是 数组,返回 []
function getMergedValue(target, source) {
      if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
        return utils.merge(target, source);
      } else if (utils.isPlainObject(source)) {
        return utils.merge({}, source);
      } else if (utils.isArray(source)) {
        return source.slice();
      }
      return source;
    }
    
  其中用 工具方法 `isPlainObject``merge`

综上所述 mergeConfig 就是合并 用户配置与 默认配置

axios.get("xxx",{ data:{a:1},f:1 }) 经过 mergeConfig结果是 { method:"get",url:"xxx",data:{a:1},f:1 }

工具方法

1. isPlainObject

 function isPlainObject(val) {
    if (kindOf(val) !== 'object') {
      return false;
    }

    var prototype = Object.getPrototypeOf(val);
    return prototype === null || prototype === Object.prototype;
  }
  
  Object.getPrototypeOf({}) === Object.prototype // true
  Object.getPrototypeOf([]) === Object.prototype // false
  Object.getPrototypeOf(new Array) === Object.prototype // false
  Object.getPrototypeOf(new Date) === Object.prototype // false

2. kindOf

通过一个函数自调用的方式进行返回一个函数,巧妙的使用了闭包,没用使用公共变量,减少了内存泄漏的风险

var toString = Object.prototype.toString;

  var kindOf = (function(cache) {
   
    return function(thing) {
    
      var str = toString.call(thing);
      
      return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
    };
    
  })(Object.create(null));

3. merge

不限制参数

 function merge(/* obj1, obj2, obj3, ... */) {
    var result = {};
    // 
    function assignValue(val, key) {
    
    // 在这个函数调用中,isPlainObject(result[key]) 和 isPlainObject(val) 
    //  let r =  merge({a:1,b:{g:6}},{a:2,b:{f:7}})
    // result[key] 是 {g:6} 
    // val 是 {f:7}
    // 第一次遍历 result[key] 是 undefined,执行下面的判断, result[key] = {b:g:6}
    // 等到第二次遍历到 下个对象 的时候,发现 result 有这个key 值,进行合并
      if (isPlainObject(result[key]) && isPlainObject(val)) {
      
        result[key] = merge(result[key], val);
        
      } else if (isPlainObject(val)) {
      // 做了一次深拷贝
        result[key] = merge({}, val); // {b:f:7}
        // result[key] = val // {b:f:8}
      } 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;
  }
  
   let d = {f:7};
   let r =  merge({a:1,b:d},{a:2,b:d})
   d.f = 8
   console.log(r)

4. forEach 方法

使用设配器设计模式,可以对 数组 / 对象执行同样的遍历操作

   /*
   * @param {Object|Array} obj The object to iterate
   * @param {Function} fn The callback to invoke for each item
   */
   
  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') {
      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);
        }
      }
    }
  }
  
  function isArray(val) {
    return Array.isArray(val);
  }

今天先写到 mergeConfig 这里,可以看出,axios 有很多 工具方法 用了很多巧思,比如 使用 forEach对数组和对象进行统一的遍历,使用merge进行不限制参数的合并,对深度合并也做了处理,kindOf中使用闭包来对数据进行一次缓存,isPlainObject 利用了 Object.getPrototypeOf(val)===Object.prototype;通过原型链来判断是否是一个普通的对象

接下来是 axios 的核心方法request

time:2022/9/28 星期三