axios工具函数

378 阅读4分钟

1. 前言

最近我参加了若川视野-源码共读的活动,本篇文章是参加第十九期活动写的。

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

2. 概览

本文分析的是axios源码中lib文件夹的文件utils.js,它包括17个类型判断方法,5个数据处理方法。源码分析的策略采用的是解释说明+源码展示,比较简单直接,如有发现任何问题望指出,不胜感激。

3. 源码分析

3.1 简单类型判定

从以下方法可以看出,基础类型判定的方法有一个是用Array.isArray,其他全用的是typeof。第一个方法用来判断array类型,第二个方法判断了undefined,string,number,object4个类型

function isArray(val) {
  return Array.isArray(val);
}

function isUndefined(val) {
  return typeof val === 'undefined';
}

function isString(val) {
  return typeof val === 'string';
}

function isNumber(val) {
  return typeof val === 'number';
}

function isObject(val) {
  return val !== null && typeof val === 'object';
}

3.2 Object.prototype.toString 方法

之所以将这个方法单独列出来,是因为工具函数里面有8个类型的判定使用了该方法。为什么都使用这个方法进行类型判定,参考这篇文章,很多数据类型都实现了自己的toString方法,比如Number.prototype.toString,它可以将数字转化成字符串。但只有Object.prototype.toString会返回对应的数据类型,所以这个方法是判断Javascript数据类型的标准方式。

// 非源码
let a=23
a.toString()   // '23'
var toString = Object.prototype.toString;

function isArrayBuffer(val) {
  return toString.call(val) === '[object ArrayBuffer]';
}

function isFormData(val) {
  return toString.call(val) === '[object FormData]';
}

function isPlainObject(val) {
  if (toString.call(val) !== '[object Object]') {
    return false;
  }

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

function isDate(val) {
  return toString.call(val) === '[object Date]';
}

function isFile(val) {
  return toString.call(val) === '[object File]';
}

function isBlob(val) {
  return toString.call(val) === '[object Blob]';
}

function isFunction(val) {
  return toString.call(val) === '[object Function]';
}

function isURLSearchParams(val) {
  return toString.call(val) === '[object URLSearchParams]';
}

3.3 其他类型判断方法

第一个方法用来判断是否为 buffer,网上很少找到这种方法,一般是 Buffer.isBuffer()。第二个方法判断是否为 ArrayBufferview ,使用了ArrayBuffer.isView()方法,没有该方法的话通过判断这个值是否存在、并且包含ArrayBuffer类型的属性buffer。第三个方法判断是否为 Stream,先判断值是否为对象,再判断它的属性pipe是否为方法。第四个方法判断是否为标准的的浏览器环境

function isBuffer(val) {
  return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
    && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}

function isArrayBufferView(val) {
  var result;
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
    result = ArrayBuffer.isView(val);
  } else {
    result = (val) && (val.buffer) && (isArrayBuffer(val.buffer));
  }
  return result;
}

function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}

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

3.4 forEach :遍历每个元素并调用给定的方法

数组或者对象的每个元素都调用一次方法,并将每个元素的键值,键名,原数组或对象传递给该方法

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);
      }
    }
  }
}

3.5 merge:对象合并的方法

遍历函数参数,每个参数调用assignValue方法。在asignValue方法中,如果result[key]val是纯对象,则递归处理两个值;如果只有val是纯对象,则递归处理val;如果val是数组,则把val的值浅拷贝赋值给result[key];否则,直接将val值赋值给result[key]

/* Example:
 * var result = merge({foo: 123}, {foo: 456});
 * console.log(result.foo); // outputs 456
 */
 
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;
}

3.6 extend:对象的属性扩展

b的每个元素调用assignValue方法,如果val是函数并且thisArg非空,则val的方法指向thisArg,并作为a属性key的值;否则直接将val作为a属性key的值。该方法最后得到扩展后的a对象

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;
}
module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};

3.7 其他方法

stripBOM()主要用来去掉Unicode的字符BOM,这个字符可能会造成一些问题,参考UTF-8编码中BOM的检测与删除trim()方法用来去掉字符串的首位空格,如果字符串没有原生的trim()方法,则使用正则表达式。

function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

function trim(str) {
  return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}

4 总结

  1. 总的来说axios的工具函数做了很多的类型校验,使用到的Object.prototype.toString.call()方法值的深入学习。另外封装的forEach、merge、extend方法也值得深入学习并运用到日常的开发中。
  2. 本文耗费了6个多小时,整体行文思路比较清晰,但源码的解释说明还是不够清晰,希望可以得到指正。