axios源码解析(until.js方法)

一. 概览

axios源码中until.js文件封装了大量的公共方法, 其封装思路和实现过程非常值得我们学习,这对于我们进一步深入掌握javascript很有帮助。
本篇文章用到的知识点主要有闭包,立即执行函数, 柯里化,递归等

二.源码解读(柯里化)

1. 类型判断
const toString = Object.prototype.toString;

// eslint-disable-next-line func-names
const kindOf = (cache => {
  // eslint-disable-next-line func-names
  return thing => {
    const str = toString.call(thing);
    return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
  };
})(Object.create(null));

function kindOfTest(type) {
  type = type.toLowerCase();
  return function isKindOf(thing) {
    return kindOf(thing) === type;
  };
}
//使用
const isDate = kindOfTest('Date');
const isFile = kindOfTest('File');
const isDataFlag = isDate(new Date())

(1)用到了立即函数,立即函数只会在一开始执行一次,一般用于初始赋值使用;
(2) 之所以初始值为Object.create(null)是为了赋值一个原型为空的空对象,一个纯净的空对象,好处比如在for in方法时,就不需要筛选自有属性这步了;
(3) 原本该函数应该调用两个参数的,一个是要判断的值数据类型,一个是值本身,一般可以利用一个函数传两个参数这样的方法;在axios源码中利用柯里化的手段,即每个函数传一个参数,函数里面返回函数的形式。
(4)因为柯里化的实现离不开闭包,所以在有些场景下具有缓存的效果

2. 柯里化特点总结

(1) 参数复用

通用函数 解决了兼容性问题,但也会带来使用的不便,比如不同的应用场景需要传递多个不同的参数来解决问 题
有的时候同一种规则可能会反复使用(比如校验手机的参数),这就造成了代码的重复,利用柯里化就能够消除重复,达到复用参数的目的;

(2) 柯里化是闭包的一个典型应用,利用闭包形成了一个保存在内存中的作用域,把接收到的部分参数保存在这个作用域中,等待后续使用。并且返回一个新函数接收剩余参数;

(3) 提前返回、延迟执行;

(4) 降低适用范围,提高适用性, 提高代码可读性

比如还有其它类似的场景也经常可使用柯里化

function checkByRegExp(regExp) {
    return function(str) {
        return regExp.test(str)
    }
}

const checkPhone = curryingCheckByRegExp(/^1\d{10}$/)
checkPhone('15152525634'); 
3. 拓展: 柯里化函数封装
 * @description: 将函数柯里化的工具函数
 * @param {Function} fn 待柯里化的函数
 * @param {array} args 已经接收的参数列表
 * @return {Function}
 */
const currying = function(fn, ...args) {
    // fn需要的参数个数
    const len = fn.length
    // 返回一个函数接收剩余参数
    return function (...params) {
        // 拼接已经接收和新接收的参数列表
        let _args = [...args, ...params]
        // 如果已经接收的参数个数还不够,继续返回一个新函数接收剩余参数
        if (_args.length < len) {
            return currying.call(this, fn, ..._args)
        }
          // 参数全部接收完调用原函数
        return fn.apply(this, _args)
    }
}

三. 源码解读(bind,isPlainObject方法)
1. bind
export default function bind(fn, thisArg) {
  return function wrap() {
    return fn.apply(thisArg, arguments);
  };
}
const instance = bind(Axios.prototype.request, context);

instance 相当于得到wrap函数,等同于处理过的fn函数本身,instance的执行相当于fn函数的执行

2. isPlainObject

isPlainObject() 函数用于判断指定参数是否是一个纯粹的对象,返回值为Boolean类型。 “纯粹的对象”,就是通过 { }、new Object()、Object.create(null) 创建的对象。 这个方法的作用是为了跟其他的 JavaScript对象如 null,数组,宿主对象(documents),DOM 等作区分,因为这些用 typeof 都会返回object。

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

  const prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

1Object.getPrototypeOf(val): 获取val原型上的数据;为null,说明是由Object.create(null)创建,为Object.prototype说明是{}或者new Object()方法生成

四. 源码解读(forEach,extend)
1. forEach
function forEach(obj, fn, {allOwnKeys = false} = {}) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  let i;
  let l;

  // 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 (i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
    const len = keys.length;
    let key;

    for (i = 0; i < len; i++) {
      key = keys[i];
      fn.call(null, obj[key], key, obj);
    }
  }
}
// 使用
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method,
      url,
      data: (config || {}).data
    }));
  };
});

forEach方法有三个参数:

  1. 第一个参数obj为所要循环的对象/数组;
  2. 第二个参数为执行的方法,该方法是使用时自定义的。该方法有三个参数,分别为遍历数组/对象子项,子项的key,遍历数组/对象本身;
  3. 第三个参数判断是否要遍历对象/数组的原型上的值
2. extend方法
function extend(a, b, thisArg, {allOwnKeys}= {}) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  }, {allOwnKeys});
  return a;
}

此方法是在forEach方法上的拓展,将b的属性赋值到a上

五. 源码解读(merge方法)
function merge(/* obj1, obj2, obj3, ... */) {
  const 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 (let i = 0, l = arguments.length; i < l; i++) {
    arguments[i] && forEach(arguments[i], assignValue);
  }
  return result;
}
  1. 主要用于多个对象的合并,如有相同属性,后面参数值会覆盖之前参数,类似于ES6中的扩展运算符;
  2. 深层结构: 循环中的forEach方法中,是对象属性的深拷贝,是数组属性的浅拷贝,所以当处理数组对象时,merge方法会改变原数据
拓展(深拷贝)
function deepClone(source) {
  if (typeof source !== 'object' || source == null) {
    return source;
  }
  const target = Array.isArray(source) ? [] : {};
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (typeof source[key] === 'object' && source[key] !== null) {
        target[key] = deepClone(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

此次有用到递归,递归的特点有如下几点

  1. 重复调用自己,且必须要有终止条件;
  2. 其中一开始会从外到内为顺序调用自身函数,最内层的函数先调用完成,然后依次由内到外调用完成调用的自身函数,此为‘归’
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值