axios源码——工具函数utils.js

有宏峻
2023-12-01


前言

学习源码,可以帮助我们工具基础知识点和提升编程能力。之前分享过vue3中的常用工具函数(戳此链接)。
那今天本文的主角是axios源码中的/lib/utils.js,介绍一下axios库中常用的工具函数


一、工具函数所在目录

axios/lib/utils.js

二、判定数据类型的函数

共用的toString 方法

var toString = Object.prototype.toString;

1.isArray(判定数组)

和vue3源码中判定一样

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

2.isString(判定字符串)

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

3.isNumber(判定数值)

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

4.isObject(判定对象)

注意在js中,null也是一个特殊对象,此时判定要排出null的情况。
此方法同vue3源码中判定对象方法

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

5.isPlainObject(判定纯对象)

纯对象: 用{}或new Object()创建的对象。
此方法同vue3源码中判定纯对象方法

function isPlainObject(val) {
  if (toString.call(val) !== '[object Object]') {
    return false;
  }
  // Object.getPrototypeOf返回指定对象的原型
  // 其实就是判断目标对象的原型是不是`null` 或 `Object.prototype`
  var prototype = Object.getPrototypeOf(val);  
  return prototype === null || prototype === Object.prototype;
}

测试代码如下

// 例子1
const o = {name: 'jay}
isPlainObject(o) // true

// 例子2
const o = new Object()
o.name = 'jay'
isPlainObject(o)   // true

// 例子3
function C() {}
const c = new C()
isPlainObject(c);  // false

6.isUndefined(判定是否是undefined)

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

7.isFunction(判定是否是Function)

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

在vue3中该方法如下:

export const isFunction = (val: unknown) => typeof val === 'function'

两种方法比较:

  • typeof 操作符返回一个字符串,表示右侧操作数的类型。对于函数来说,会返回字符串 “function”。这种方式适用于各种函数,例如命名的函数,赋值到变量的函数,箭头函数等。(不过需要注意:部分浏览器(例如在 Chrome 57 )存在 bug,对于 HTML 的 object 元素和 embed 元素上运行 typeof 会返回 “function”。
  • toString() 方法检查一个变量的类型,它会获得变量的字符串表现形式,然后和字符串 ‘[object Function]’进行比对。不过这种方式不可靠,不能正确识别 复制到变量的函数、 ES 6 的 class 和异步函数。
  • 综合对比,优选typeof 方法判定function

测试代码如下:

// 1、普通function函数
let a = function(){console.log(1)};
console.log(typeof a);   // function
console.log(Object.prototype.toString.call(a)); // '[object Function]'

// 2、赋值到变量的函数
let b = function(){console.log(1)};
console.log(typeof b);  // function
console.log(Object.prototype.toString(b));  // '[object Object]'

// 3、ES6 class
class C{}
console.log(typeof C);  // function
console.log(Object.prototype.toString.call(C)); // '[object Object]'

8.isDate(判定是否是Date对象)

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

vue3中该方法如下:

export const isDate = (val: unknown) => val instanceof Date

两种方法对比:

  • 首先判定Date,不能使用~~typeof~~ ,因为会返回object
  • 我们通常创建一个Date对象,使用的是new Date(),通过new关键字创建的可以使用instanceofinstanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上)判定。同时toString方法也可以准确得到'[object Date]'
  • 综上所述,以上两种方法均可

9.isFile(判定是否是File文件对象)

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

10.isBlob(判定是否是Blob对象)

  • Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取。
function isBlob(val) {
  return toString.call(val) === '[object Blob]';
}

11.isBuffer(判定是否是Buffer对象)

  • JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
  • axios可以运行在浏览器和node环境中,所以内部会用到nodejs相关的知识
  • 在v6.0之前创建Buffer对象直接使用new Buffer()构造函数来创建对象实例,但是Buffer对内存的权限操作相比很大,可以直接捕获一些敏感信息,所以在v6.0以后,官方文档里面建议使用 Buffer.from() 接口去创建Buffer对象。
// 最终是通过Buffer.isBuffer()方法去判定
function isBuffer(val) {
  return val !== null             
  		&& !isUndefined(val)   // 先判断不是 `undefined`和`null`
  		&& val.constructor !== null 
  		&& !isUndefined(val.constructor)  // 再判断 `val`存在构造函数,因为`Buffer`本身是一个类
  		&& typeof val.constructor.isBuffer === 'function'  
  		&& val.constructor.isBuffer(val);  // 最后判定构造函数Buffer有`isBuffer`方法,并且用自身`isBuffer`判断
}

测试代码如下:

const buf = Buffer.from([1, 2, 3]);
console.log(typeof buf)  // object
console.log(buf instanceof Buffer);  // true
console.log(buf.constructor.isBuffer(buf))  // true

12.isArrayBuffer(判定是否是ArrayBuffer对象)

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

13.isFormData(判定是否是FormData对象)

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

测试代码:

let form = new FormData();
form.append('namme',12);
Object.prototype.toString.call(form)
'[object FormData]'

14.isStream(判定是否是Stream)

  • 这里isObjectisFunction为上文提到的方法。
  • 是对象,也是函数。就是流
function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}

15.isURLSearchParams(判定是否是URLSearchParams对象)

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

测试代码如下

const paramsString = "name=mj&age=13"
const searchParams = new URLSearchParams(paramsString);
console.log(isURLSearchParams(searchParams)) // true

三、其他常用的工具方法

1.trim(去除首尾空格)

  • trim方法不存在的话,用正则匹配
function trim(str) {
  return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}

2.isStandardBrowserEnv(判定标准浏览器环境)

  • axios可运行在web worker、react-native中
  • 但是官方已经不推荐使用这个属性navigator.product。
/**
 *
 * 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'
  );
}

3.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') {
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values。是数组,for循环执行回调fn
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys。是对象,for in循环执行回调fn
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

4.merge(合并对象)

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

5.extend(对象属性扩展)

  • 将一个对象的方法和属性扩展到另外一个对象上,并为对象方法指定上下文(this指向)
  • 遍历b,把b中所有属性赋值给a,并且若属性值为方法以及给出了上下文对象thisArg,则需要改变function的上下文对象。
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
  	// 判断传入了上下文对象,以及 b 中 属性值val 是方法,则需要绑定函数执行上下文对象
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);  // 绑定函数的执行上下文对象
    } else {
    // 若并未传入上下文对象,或者不是函数,直接赋值
      a[key] = val;
    }
  });
  return a;
}

6.stripBOM(删除UTF-8编码中BOM)

  • BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码,但它对于文件的读者来说是不可见字符。
  • Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,即BOM.
  • UTF-8 是 Unicode 的实现方式之一。Unicode码4E25UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。
  • UTF-8是与字节顺序无关的,所以UTF-8编码文件通常要删除掉BOM
function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

总结

本文介绍了一些utils.js中的非常实用的工具函数;相信通过阅读源码,日积月累,并把这些代码或思想应用的自己项目中去,相信能够很好的提升自己的编码能力。

  • is开头的函数通常用来判断变量类型
  • 引用类型的数据通常用Object.prototype.toString.call(val)===’[object Array]'来判断
  • 基础数据类型,通常使用typeof去判断
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
 类似资料: