utils
is a library of generic helper functions non-specific to axios
utils
是一个非特定于 axios 的通用辅助函数库
# 一、环境准备
axios
版本v0.24.0
通过
github1s
网页可以 查看 (opens new window) axios 源码调试需要
clone
到本地
git clone https://github.com/axios/axios.git
cd axios
npm start
http://localhost:3000/
# 二、函数研读
# 1. helper 函数
var bind = require('./helpers/bind');
===>
'use strict';
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);
};
};
Tips: 这是一个配合后述extend
工具函数使用的方法,作用是当运行时存在明确的this
指向即thisArg
时将fn
扩展(添加)到目标对象中,如图中的 showInfo
# 2. 使用toString()
获取对象类型
可以通过
toString()
来获取每个对象的类型,关于 toString 更多的性质,详情见toString
# 【2.1】 isArray
var toString = Object.prototype.toString;
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
function isArray(val) {
return toString.call(val) === "[object Array]";
}
- isArray 封装了 Object 原型链函数 toString(),借助 toString()判断属性类型的性质判断 val 是否为数组
- Object 原型链函数 toString()在成功判断数组时固定返回'[object Array]'
- 关于 Array 类型判断详情见ECMA-262 (opens new window) - Let isArray be IsArray(Object).
Tips: 从ECMA-262
文档可以看出,从 es6 后 toString()在判定 Array 类型时直接使用了 IsArray 方法,所以如果环境允许,直接使用 Array.isArray()也是可行的 🐶,这点在MDN
- Array.isArray (opens new window)的 Polyfill 中也是有体现的。
Tips: Array.isArray()
是 es5 特性
# 【2.2】 isArrayBuffer
/**
* Determine if a value is an ArrayBuffer
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an ArrayBuffer, otherwise false
*/
function isArrayBuffer(val) {
return toString.call(val) === "[object ArrayBuffer]";
}
- 并未在 MDN 上找到示例
- 关于 ArrayBuffer 更多的性质,详情见ArrayBuffer
# 【2.3】 isDate
/**
* Determine if a value is a Date
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Date, otherwise false
*/
function isDate(val) {
return toString.call(val) === "[object Date]";
}
- isArray 封装了 Object 原型链函数 toString(),借助 toString()判断属性类型的性质判断 val 是否为日期类型
- Object 原型链函数 toString()在成功判断数组时固定返回'[object Date]'
- 关于 Date 类型判断详情见ECMA-262 (opens new window) - if Object has a [[DateValue]] internal slot, let builtinTag be "Date".
# 【2.4】 isFile
/**
* Determine if a value is a File
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a File, otherwise false
*/
function isFile(val) {
return toString.call(val) === "[object File]";
}
- 并未在 MDN 上找到示例
# 【2.5】 isBlob
/**
* Determine if a value is a Blob
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Blob, otherwise false
*/
function isBlob(val) {
return toString.call(val) === "[object Blob]";
}
- 并未在 MDN 上找到示例
# 【2.6】 isFunction
/**
* Determine if a value is a Function
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Function, otherwise false
*/
function isFunction(val) {
return toString.call(val) === "[object Function]";
}
- isArray 封装了 Object 原型链函数 toString(),借助 toString()判断属性类型的性质判断 val 是否为 Function 函数类型
- Object 原型链函数 toString()在成功判断数组时固定返回'[object Array]'
- 关于 Function 类型判断详情见ECMA-262 (opens new window) - if Object has a [[Call]] internal method, let builtinTag be "Function".
# 3. 使用 typeof
获取未经计算的操作数
可以通过
typeof
来获取未经计算的操作数
的类型,关于 typeof 更多的性质,详情见typeof
# 【3.1】 isUndefined
/**
* Determine if a value is undefined
*
* @param {Object} val The value to test
* @returns {boolean} True if the value is undefined, otherwise false
*/
function isUndefined(val) {
return typeof val === "undefined";
}
# 【3.2】 isString
/**
* Determine if a value is a String
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a String, otherwise false
*/
function isString(val) {
return typeof val === "string";
}
# 【3.3】 isNumber
/**
* Determine if a value is a Number
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Number, otherwise false
*/
function isNumber(val) {
return typeof val === "number";
}
# 【3.4】 isObject
/**
* Determine if a value is an Object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Object, otherwise false
*/
function isObject(val) {
return val !== null && typeof val === "object";
}
_.isObject({});
// => true
_.isObject([1, 2, 3]);
// => true
_.isObject(_.noop);
// => true
_.isObject(null);
// => false
- 检查 value 是否是普通对象,即排除掉 null 类型的所有对象类型,包含 array、date 等对象类型
# 4. 使用 instanceof
检测当前实例原型链是否包含 prototype 属性
可以通过
instanceof
运算符检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,关于 instanceof 更多的性质,详情见instanceof
# 【4.1】isURLSearchParams
/**
* Determine if a value is a URLSearchParams object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
*/
function isURLSearchParams(val) {
return (
typeof URLSearchParams !== "undefined" && val instanceof URLSearchParams
);
}
- 关于 isURLSearchParams( URL 的查询字符串 ) 更多的性质,详情见URLSearchParams
# 5. 复合类型
通过
toString()
、typeof
、instanceof
等 API 及上述工具函数功能组合
# 【5.1】isBuffer
/**
* Determine if a value is a Buffer
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Buffer, otherwise false
*/
function isBuffer(val) {
return (
val !== null &&
!isUndefined(val) &&
val.constructor !== null &&
!isUndefined(val.constructor) &&
typeof val.constructor.isBuffer === "function" &&
val.constructor.isBuffer(val)
);
}
- 先判断不是 undefined 和 null,再判断
val
存在构造函数
,因为Buffer
本身是一个类,最后通过自身的isBuffer
方法判断 - axios 可以运行在浏览器和 node 环境中,所以内部会用到 nodejs 相关的 Buffer 类知识。
- 关于 Buffer 更多的性质,详情见Buffer
# 【5.2】 isArrayBufferView
/**
* Determine if a value is a view on an ArrayBuffer
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false
*/
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;
}
- 先判断不是 undefined 和 再判断
ArrayBuffer
原型链存在isView
方法,如果原型链存在 isView 方法,则使用ArrayBuffer.isView()
判断,否则调用上述封装的isArrayBuffer()
方法 - 关于 ArrayBuffer 更多的性质,详情见ArrayBuffer
# 【5.3】 isPlainObject
/**
* Determine if a value is a plain Object
*
* @param {Object} val The value to test
* @return {boolean} True if value is a plain Object, otherwise false
*/
function isPlainObject(val) {
if (toString.call(val) !== "[object Object]") {
return false;
}
var prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
function Foo() {
this.a = 1;
}
_.isPlainObject(new Foo());
// => false
_.isPlainObject([1, 2, 3]);
// => false
_.isPlainObject({ x: 0, y: 0 });
// => true
_.isPlainObject(Object.create(null));
// => true
- 判断目标对象的原型是不是
null
或Object.prototype
- 顾名思义,目标对象在原型链顶端或者其原型为 Object 类型,是纯粹的对象
# 【5.4】 isFormData
/**
* Determine if a value is a FormData
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an FormData, otherwise false
*/
function isFormData(val) {
return typeof FormData !== "undefined" && val instanceof FormData;
}
- 关于 FormData 更多的性质,详情见FormData
# 【5.5】 isStream
/**
* Determine if a value is a Stream
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Stream, otherwise false
*/
function isStream(val) {
return isObject(val) && isFunction(val.pipe);
}
- 关于 streams 流更多的概念参考
MDN
- Streams API (opens new window)
# 6. 正则表达式
# 【6.1】 trim
/**
* Trim excess whitespace off the beginning and end of a string
*
* @param {String} str The String to trim
* @returns {String} The String freed of excess whitespace
*/
function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, "");
}
- 去除首尾空格,str 原型链不存在
trim
方法的话就使用正则表达式 - 关于正则表达式更多内容见正则表达式思维导图(学习补充 ing) (opens new window)
# 7. 重写
# 【7.1】 forEach
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @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") {
/*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);
}
}
}
}
- 如果是 null 或 undefined 直接返回
- 如果不可遍历则转换成 Array
- 如果是 Array 类型直接循环遍历
- 如果是 Object 类型则使用原型链上的
hasOwnProperty()
方法遍历,值得注意的是不推荐使用对象实例上的hasOwnProperty()
,关于这一点参考eslint no-prototype-builtins (opens new window)
Tips: 这是一个配合后述merge
与extend
工具函数的方法,因此 call
中必填项thisArg
未设置指向
# 8. js strikes
# 【8.1】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;
}
作用是合并有相同键的 val 值
配合前述
forEach
工具函数,根据arguments
长度循环遍历其中的每一项,需要注意的是arguments[i]
有可能是对象 Object、数组 Array、null 或者未定义 undefined首先进入 for 循环,其中
arguments
(opens new window) 是一个对应于传递给函数的参数的类数组对象每次遍历时调用
assignValue
方法,入参为forEach
工具函数返回的内容第一层 if 判断
result
中是否有键为key
的纯对象,并且forEach
返回的val
即obj[key]
是否是纯对象,满足条件则进入递归,将val
合并至result
对应的键key
下第二层 else if
result
中是不含键为key
的纯对象,并且val
值是纯对象,满足条件则进入递归,在result
中新建一个键为key
的,值为val
的纯对象第三层 else if
val
值是数组,满足条件则进入递归,在result
中新建一个键为key
的,值为val
的对象,其中val.slice()
作用是拷贝数据
# 【8.2】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;
}
- 作用是将
Object b
的属性和方法添加到Object a
上 - 如果待添加的
val
是function
且运行时存在明确的this
指向thisArg
,需要通过调用bind
绑定至当前对象即a
- 如果待添加的
val
是property
,直接在a
上添加相应的key-val
键值对
# 【8.3】stripBOM
/**
* 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,BOM)是位于码点 U+FEFF 的统一码字符的名称。当以 UTF-16 或 UTF-32 来将 UCS/统一码字符所组成的字符串编码时,这个字符被用来标示其字节序,更多内容可以参考
MDN
- TextDecoder (opens new window)
# 9 环境
# 【9.1】 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";
}
- 判断标准浏览器环境,官方不再推荐navigator.product (opens new window)
# 三、参考
1. Ethan01
的文章阅读 axios 源码,发现了这些实用的基础工具函数 (opens new window)
2. 李冰
老师的专栏图解 Google V8 - 一篇文章彻底搞懂 JavaScript 的函数特点 (opens new window)
← Axios Axios实例化配置函数 →