【若川视野 x 源码共读】第19期 | axios 工具函数

145 阅读9分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

1. 前言

本文是参与第二次若川大佬组织的源码阅读活动,上一期源码阅读时也有了非常非常多的收获,其中最大的收获莫过于认识到源码阅读其实并没有曾经印象中的那么神秘,那么遥远,那么困难;因此趁热打铁,参与到了新的一期源码阅读中,也希望看到这篇文章的读者可以参与到这个活动中,当认真完成一期后相信你也会有很多收获。学无止境,路漫漫其修远兮吾将上下而求索,加油!!

2. 工具函数

kindOf

获取类型。

 var toString = Object.prototype.toString;
 var kindOf = (function(cache) {
   // eslint-disable-next-line func-names
   return function(thing) {
     var str = toString.call(thing);
     return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
   };
 })(Object.create(null));
  • toStringvue2工具函数中也遇到过,输出一个表示该对象的字符串,但这里的输出结果为小写字符串。
  • 定义一个自执行函数,传入一个空对象作为参数同时作为kindOf方法的缓存,保存toString输出的类型及对应的小写字符串。

kindOfTest

生成获取类型的测试函数。

 function kindOfTest(type) {
   type = type.toLowerCase();
   return function isKindOf(thing) {
     return kindOf(thing) === type;
   };
 }
  • 函数传入一个类型字符串如:string(不区分大小写),返回一个函数isKindOf,该函数用于判断传入对象是否为该类型。

isArray

是否为数组

 function isArray(val) {
   return Array.isArray(val);
 }
  • 使用Array对象原生方法isArray进行判断。

isUndefined

是否为undefined

 function isUndefined(val) {
   return typeof val === 'undefined';
 }
  • 类型严格为undefined,不同于vue2的工具函数null也被判定为undefined

isBuffer

是否为Buffer对象

 function isBuffer(val) {
   return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
     && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
 }
  • BufferNode.js提供的对象,一般用于IO操作,应为axios可用于Node.js,因而会用不同于单纯前端项目的一些定义;
  • 对象不为null且不为undefined且构造函数的引用不为null且构造函数的引用不为undefined且原型包含方法isBuffer且原型方法isBuffer判断结果为true

isArrayBuffer

是否为ArrayBuffer对象

 var isArrayBuffer = kindOfTest('ArrayBuffer');
  • 使用kindOfTest方法生成类型测试方法

isArrayBufferView

是否为ArrayBufferView MDN

 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;
 }
  • 判断是否存在ArrayBuffer对象和isView方法,若存在,ArrayBuffer.isView()方法进行判断
  • 否则,依次判断valval.bufferval.buffer是否为ArrayBuffer

isString

是否为string

 function isString(val) {
   return typeof val === 'string';
 }
  • 根据typeof的结果进行判断

isNumber

是否为number

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

isObject

是否为object

 function isObject(val) {
   return val !== null && typeof val === 'object';
 }
  • val不为nullval类型为object,与vue2工具函数中的判断相同

isPlainObject

判断是否为PlainObject

 function isPlainObject(val) {
   if (kindOf(val) !== 'object') {
     return false;
   }
 ​
   var prototype = Object.getPrototypeOf(val);
   return prototype === null || prototype === Object.prototype;
 }
  • PlainObject纯粹对象,通过{}或者new Object()创建的对象
  • 类型需要为object
  • prototype === null || prototype === Object.prototype对象原型为null(没有继承)或继承的也是对象
  • Object.getPrototypeOf(val)返回指定对象的原型 MDN

isDate

判断是否为Date日期对象

 var isDate = kindOfTest('Date');
  • 使用kindOfTest创建判断函数

isFile

判断是否为File文件对象

 var isFile = kindOfTest('File');
  • 使用kindOfTest创建判断函数,没什么多说的
  • File对象的详细说明 MDN

isBlob

判断是否为Blob对象

 var isBlob = kindOfTest('Blob');

isFileList

判断是否为FileList对象

 var isFileList = kindOfTest('FileList');

isFunction

是否为方法

 function isFunction(val) {
   return toString.call(val) === '[object Function]';
 }
  • toString.call(val) === '[object Function]'对象的字符串表示为Function

isStream

是否为Stream流对象

 function isStream(val) {
   return isObject(val) && isFunction(val.pipe);
 }
  • 首先isObject(val)判断是否是对象
  • 其次isFunction(val.pipe)判断对象是否存在pipe这个方法
  • Stream应该为Node.js中的对象,在前端中没有找到 Stream Node.js

isFormData

判断是否为FormData表单数据对象

 function isFormData(thing) {
   var pattern = '[object FormData]';
   return thing && (
     (typeof FormData === 'function' && thing instanceof FormData) ||
     toString.call(thing) === pattern ||
     (isFunction(thing.toString) && thing.toString() === pattern)
   );
 }
  • 对象存在且对象原型链上存在FormData(继承自FormData)且对象字符串表示为[Obbject FormData]并且存在toString方法

isURLSearchParams

是否为URLSearchParams

 var isURLSearchParams = kindOfTest('URLSearchParams');
  • URLSearchParams对象的主要用来处理URL的查询字符串,在Web Worker中可用
  • MDN

trim

去除字符串前后空格

 function trim(str) {
   return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
 }
  • 尝试使用对象自身trim方法,若不存在,则使用replace进行替换

isStandardBrowserEnv

判断是否是标准浏览器环境

 /**
  * Determine if we're running in a standard browser environment
  *
  * This allows axios to run in a web worker, and react-native.
  * Both environments support XMLHttpRequest, but not fully standard globals.
  *
  * web workers:
  *  typeof window -> undefined
  *  typeof document -> undefined
  *
  * react-native:
  *  navigator.product -> 'ReactNative'
  * nativescript
  *  navigator.product -> 'NativeScript' or 'NS'
  */
 function isStandardBrowserEnv() {
   if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
                                            navigator.product === 'NativeScript' ||
                                            navigator.product === 'NS')) {
     return false;
   }
   return (
     typeof window !== 'undefined' &&
     typeof document !== 'undefined'
   );
 }
  • 阅读注释可知,当axios运行在web worker中时typeof window === undefinedtypeof document === undefined
  • react-nativenativescript环境下时,navigator.product等于ReactNativeNativeScriptNS
  • 当上面条件都不满足时表示当前环境为标准的浏览器环境

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);
       }
     }
   }
 }
  • 可遍历任意对象,遍历对每项执行回调方法fn
  • 当在遍历对象时,虽然使用了for...in会返回原型链上的所有属性,但使用了Object.prototype.hasOwnProperty忽略了继承属性

merge

合并对象方法

 /**
  * Accepts varargs expecting each argument to be an object, then
  * immutably merges the properties of each object and returns result.
  *
  * When multiple objects contain the same key the later object in
  * the arguments list will take precedence.
  *
  * Example:
  *
  * ```js
  * var result = merge({foo: 123}, {foo: 456});
  * console.log(result.foo); // outputs 456
  * ```
  *
  * @param {Object} obj1 Object to merge
  * @returns {Object} Result of all merge properties
  */
 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;
 }
  • 遍历传入参数,对参数每一项使用forEach进行遍历,遍历的回调方法为assignValue

  • assignValue完成工作如下:

    1. result[key]为纯粹对象,且val也是纯粹对象,则将result[key]valmerge结果赋值给result[key]

    2. 若只有val为纯粹对象,则将{}valmerge结果赋值给result[key]`

    3. 如果val为数组对象,则将val数组浅复制给result[key]

      slice 不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:

      • 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
      • 对于字符串、数字及布尔值来说(不是 StringNumber 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

      如果向两个数组任一中添加了新元素,则另一个不会受到影响。

      MDN

    4. 其它情况,直接将val赋值给result[key]

    5. 返回最终结果result

extend

继承对象

 /**
  * Extends object a by mutably adding to it the properties of object b.
  *
  * @param {Object} a The object to be extended
  * @param {Object} b The object to copy properties from
  * @param {Object} thisArg The object to bind function to
  * @return {Object} The resulting value of object 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;
 }
  • 使用forEach遍历对象b
  • val为函数时,a[key] = bind(val, thisArg);thisArg参数绑定到对象方法val
  • 否则,将val赋值给a[key]
  • 返回继承完对象b属性和方法的对象a

stripBOM

去除BOM

 /**
  * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
  *
  * @param {string} content with BOM
  * @return {string} content value without BOM
  */
 function stripBOM(content) {
   if (content.charCodeAt(0) === 0xFEFF) {
     content = content.slice(1);
   }
   return content;
 }
  • BOM(Byte Order Mark)字节顺序标记,出现在文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。

  • 不同编码的字节顺序标记的表示

    编码表示 (十六进制)表示 (十进制)
    UTF-8EF BB BF239 187 191
    UTF-16(大端序)FE FF254 255
    UTF-16(小端序)FF FE255 254
    UTF-32(大端序)00 00 FE FF0 0 254 255
    UTF-32(小端序)FF FE 00 00255 254 0 0
    UTF-72B 2F 76和以下的一个字节:[ 38392B2F ]43 47 118和以下的一个字节:[ 56574347 ]
    en:UTF-1F7 64 4C247 100 76
    en:UTF-EBCDICDD 73 66 73221 115 102 115
    en:Standard Compression Scheme for Unicode0E FE FF14 254 255
    en:BOCU-1FB EE 28及可能跟随着FF251 238 40及可能跟随着255
    GB-1803084 31 95 33132 49 149 51

inherits

继承属性方法

 /**
  * Inherit the prototype methods from one constructor into another
  * @param {function} constructor
  * @param {function} superConstructor
  * @param {object} [props]
  * @param {object} [descriptors]
  */
 ​
 function inherits(constructor, superConstructor, props, descriptors) {
   constructor.prototype = Object.create(superConstructor.prototype, descriptors);
   constructor.prototype.constructor = constructor;
   props && Object.assign(constructor.prototype, props);
 }
  • Object.create()创建一个新对象,使用现有的对象来提供新创建的对象的proto,更多说明参见MDN
  • Object.assign()将所有可枚举属性的值从一个或多个源对象分配到目标对象并返回目标对象。

toFlatObject

转化为扁平对象

 /**
  * Resolve object with deep prototype chain to a flat object
  * @param {Object} sourceObj source object
  * @param {Object} [destObj]
  * @param {Function} [filter]
  * @returns {Object}
  */
 ​
 function toFlatObject(sourceObj, destObj, filter) {
   var props;
   var i;
   var prop;
   var merged = {};
 ​
   destObj = destObj || {};
 ​
   do {
     props = Object.getOwnPropertyNames(sourceObj);
     i = props.length;
     while (i-- > 0) {
       prop = props[i];
       if (!merged[prop]) {
         destObj[prop] = sourceObj[prop];
         merged[prop] = true;
       }
     }
     sourceObj = Object.getPrototypeOf(sourceObj);
   } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);
 ​
   return destObj;
 }
  • 将原型链比较深的对象转换为平面对象
  • Object.getOwnPropertyNames()返回对象所有自身属性的属性名组成的数组 MDN
  • sourceObj = Object.getPrototypeOf(sourceObj);将源对象赋值为源对象的原型对象
  • 将元对象原型链上所有对象的属性赋值给目标对象

endsWith

判断字符串结尾是否为指定字符串

 /*
  * determines whether a string ends with the characters of a specified string
  * @param {String} str
  * @param {String} searchString
  * @param {Number} [position= 0]
  * @returns {boolean}
  */
 function endsWith(str, searchString, position) {
   str = String(str);
   if (position === undefined || position > str.length) {
     position = str.length;
   }
   position -= searchString.length;
   var lastIndex = str.indexOf(searchString, position);
   return lastIndex !== -1 && lastIndex === position;
 }
  • 首先保证传入str为字符串
  • 若未指定position或指定的位置超过字符串长度,则将position初始化为字符串长度
  • position -= searchString.length减去需要匹配字符串的长度,保证起始位置肯定在需要匹配的字符串之前

toArray

将类数组对象转化为数组

 function toArray(thing) {
   if (!thing) return null;
   var i = thing.length;
   if (isUndefined(i)) return null;
   var arr = new Array(i);
   while (i-- > 0) {
     arr[i] = thing[i];
   }
   return arr;
 }
  • Vue2中转化函数类似,只是不能指定起始位置

isTypedArray

是否为类型化数组 TypedArray MDN

 var isTypedArray = (function(TypedArray) {
   // eslint-disable-next-line func-names
   return function(thing) {
     return TypedArray && thing instanceof TypedArray;
   };
 })(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array));
  • 类型化数组描述了一个底层二进制数据缓冲区的类数组视图,并没有TypedArray的构造函数

     // TypedArray 指的是以下的其中之一:
     ​
     Int8Array();
     Uint8Array();
     Uint8ClampedArray();
     Int16Array();
     Uint16Array();
     Int32Array();
     Uint32Array();
     Float32Array();
     Float64Array();
    

3. 总结

  可能是因为适逢劳动假期的原因,自我感觉源码读的速度比之前快了些,也可能没有;但不管怎么说两期源码阅读下来确实学到了很多东西,比如之前并不知道的自执行函数,刚学到的第二天就运用到了实际工作中并解决了问题,当时的那样一种成就感,脑内多巴胺爆发的感觉,也是让我能不断坚持前进的动力,相信也是大家的动力;也希望各位读者能不断进步,努力不一定会有期望的成果,但放弃必然无法到达彼岸,加油!!