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

260 阅读8分钟

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

1. 学习准备

2. 学习目标

  1. javascript、nodejs调试技巧及调试工具;
  2. 如何学习调试axios源码;
  3. 如何学习优秀开源项目的代码,应用到自己的项目;
  4. axios源码中实用的工具函数;

3. 学习过程

3.1 判断数组的方法

  1. Array.isArray()
Array.isArray([]) // true
  1. instanceof
[] instanceof Array // true
  1. Object.prototype.toString.call([]) === '[object Array]'
Object.prototype.toString.call([]) === '[object Array]'; // true
  1. Object.prototype.isPrototypeOf()

使用Object的原型方法isPrototypeOf,判断两个对象的原型是否一样, isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。

const arr = []; 
Object.prototype.isPrototypeOf(arr, Array.prototype); // true
  1. Object.getPrototypeOf()

Object.getPrototypeOf()方法返回指定对象的原型(内部 [[Prototype]] 属性的值)。

const arr = [];
Object.getPrototypeOf(arr) === Array.prototype // true

3.2 判断undefined

// 方法1:直接用`typeof`判断 
// 注意 typeof null === 'object' 
function isUndefined(val) { 
    return typeof val === 'undefined'; 
}

// 方法2:直接使用值判断
function isUndefined(val) { 
    return val === 'undefined'; 
}

3.3 isObject 判断对象

// 排除 `null`的情况
function isObject(val) {
  return val !== null && typeof val === 'object';
}

追问:使用typeof xxx === "object"来判定xxx是否是对象有什么缺陷?该怎么改进?

解答:使用typeof并不能准确判断xxx就是一个Object

可以通过 Object.prototype.toString.call(bar) === "[object Object]" 这种方式来避免上面这种弊端, 比如:Array.isArray(myObj) 或者 Object.prototype.toString.call(myObj) === "[object Array]" 来安全的检测传过来的对象是否是一个数组。

typefo扩展:

typeof 返回一个表示数据类型的字符串,表达式: typeof xxx,返回结果包括:number、boolean、string、object、undefined、function 、symbol 7种数据类型。例如:

typeof 12; // number 
typeof NaN; // number 
typeof 'bla' ; // string 
typeof true; // boolean 
typeof Symbol(); // symbol 
typeof undefined; // undefined 
typeof function() {}; // function

注意:typeof null === 'object'; // JavaScript 诞生以来便如此;

// typeof无法区分object里面的具体类型,例如: 
typeof {a: 1} === 'object';
typeof [1, 2, 4] === 'object'; 
typeof new Date() === 'object'; 
typeof /regex/ === 'object'; 
// 使用 Array.isArray 或者 Object.prototype.toString.call 来区分数组和普通对象 

// 下面的例子令人迷惑,非常危险,没有用处。避免使用它们。 
typeof new Boolean(true) === 'object'; 
typeof new Number(1) === 'object'; 
typeof new String('abc') === 'object';

3.4 isDate 判断Date

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

3.5 isFile 判断文件类型

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

3.6 isBlob 判断Blob

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

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取。

3.7 isFunction 判断函数

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

3.8 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.9 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.10 trim 去除首尾空格

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

3.11 toArray 将字符串转成数组

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

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

function toArray(thing) {
  if (!thing) return null;
  if (isArray(thing)) return thing;
  var i = thing.length;
  if (!isNumber(i)) return null;
  var arr = new Array(i);
  while (i-- > 0) {
    arr[i] = thing[i];
  }
  return arr;
}
toArray('hello'); // ['h', 'e', 'l', 'l', 'o']

我还是更喜欢使用ES6的Array.from()来转换数组。

Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']

const arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
Array.from(arrayLike); // ['a', 'b', 'c']

3.12 isURLSearchParams 判断URLSearchParams

function isURLSearchParams(val) {
  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}


// 例子
const paramsString = "q=URLUtils.searchParams&topic=api"
const searchParams = new URLSearchParams(paramsString);
isURLSearchParams(searchParams) // true

instanceof

instanceof判断对象A是否为对象B的实例,其内部运行机制是判断在其原型链中能否找到该类型的原型。 表达式: A instanceof B

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。

instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

// 注意: `instanceof` 检测的是原型, 
// 而[]的原型指向Array.prototype,Array.prototype的原型又指向Object.prototype, 因此
[] instanceof Array;  // true
[] instanceof Object; // true

// 同理,new Date() ---> Date.prototype ---> Object.prototype
new Date() instanceof Date    // true
new Date() instanceof Object   // true

// function fn() {} ---> Function ---> Object
fn instanceof Function; // true
fn instanceof Object; // true

URLSearchParams

URLSearchParams对象是浏览器的原生对象,用来构造、解析和处理 URL 的查询字符串(即 URL 问号后面的部分)。它本身也是一个构造函数,可以生成实例。参数可以为查询字符串,起始的问号?有没有都行,也可以是对应查询字符串的数组或对象。

// 方法一:传入字符串
var params = new URLSearchParams('?foo=1&bar=2');
// 等同于
var params = new URLSearchParams(document.location.search);

// 方法二:传入数组
var params = new URLSearchParams([['foo', 1], ['bar', 2]]);

// 方法三:传入对象
var params = new URLSearchParams({'foo' : 1 , 'bar' : 2});

URLSearchParams会对查询字符串自动编码,如下所示,foo的值是汉字,URLSearchParams对其自动进行 URL 编码。

var params = new URLSearchParams({'foo': '你好'});
params.toString() // "foo=%E4%BD%A0%E5%A5%BD"

浏览器向服务器发送表单数据时,可以直接使用URLSearchParams实例作为表单数据。

const params = new URLSearchParams({foo: 1, bar: 2});
fetch('https://example.com/api', {
  method: 'POST',
  body: params
}).then(...)
复制代码

上面代码中,fetch命令向服务器发送命令时,可以直接使用URLSearchParams实例。

URLSearchParams可以与URL()接口结合使用。

var url = new URL(window.location);
var foo = url.searchParams.get('foo') || 'somedefault';

上面代码中,URL 实例的searchParams属性就是一个URLSearchParams实例,所以可以使用URLSearchParams接口的get方法。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});

for (var p of params) {
  console.log(p[0] + ': ' + p[1]);
}
// foo: 1
// bar: 2

URLSearchParams没有实例属性,只有实例方法。

image.png

URLSearchParams.toString()

toString方法返回实例的字符串形式。

var url = new URL('https://example.com?foo=1&bar=2');
var params = new URLSearchParams(url.search);

params.toString() // "foo=1&bar=2'

那么需要字符串的场合,会自动调用toString方法。

var params = new URLSearchParams({version: 2.0});
window.location.href = location.pathname + '?' + params;

上面代码中,location.href赋值时,可以直接使用params对象。这时就会自动调用toString方法。

URLSearchParams.append()

append()方法用来追加一个查询参数。它接受两个参数,第一个为键名,第二个为键值,没有返回值。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('baz', 3);
params.toString() // "foo=1&bar=2&baz=3"

append()方法不会识别是否键名已经存在,比如下面的代码,查询字符串里面foo已经存在了,但是append依然会追加一个同名键。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('foo', 3);
params.toString() // "foo=1&bar=2&foo=3"

URLSearchParams.delete()

delete()方法用来删除指定的查询参数。它接受键名作为参数。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.delete('bar');
params.toString() // "foo=1"

URLSearchParams.has()

has()方法返回一个布尔值,表示查询字符串是否包含指定的键名。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.has('bar') // true
params.has('baz') // false

URLSearchParams.set()

set()方法用来设置查询字符串的键值。

它接受两个参数,第一个是键名,第二个是键值。如果是已经存在的键,键值会被改写,否则会被追加。

var params = new URLSearchParams('?foo=1');
params.set('foo', 2);
params.toString() // "foo=2"
params.set('bar', 3);
params.toString() // "foo=2&bar=3"

上面代码中,foo是已经存在的键,bar是还不存在的键。

如果有多个的同名键,set会移除现存所有的键。

var params = new URLSearchParams('?foo=1&foo=2');
params.set('foo', 3);
params.toString() // "foo=3"

下面是一个替换当前 URL 的例子。

// URL: https://example.com?version=1.0
var params = new URLSearchParams(location.search.slice(1));
params.set('version', '2.0');

window.history.replaceState({}, '', location.pathname + `?` + params);
// URL: https://example.com?version=2.0

URLSearchParams.get()

get()方法用来读取查询字符串里面的指定键。它接受键名作为参数。

var params = new URLSearchParams('?foo=1');
params.get('foo') // "1"
params.get('bar') // null

两个地方需要注意:

  • 第一,它返回的是字符串,如果原始值是数值,需要转一下类型;
  • 第二,如果指定的键名不存在,返回值是null

如果有多个的同名键,get返回位置最前面的那个键值。

var params = new URLSearchParams('?foo=3&foo=2&foo=1');
params.get('foo') // "3"

上面代码中,查询字符串有三个foo键,get方法返回最前面的键值3

URLSearchParams.getAll()

getAll()方法返回一个数组,成员是指定键的所有键值。它接受键名作为参数。

var params = new URLSearchParams('?foo=1&foo=2');
params.getAll('foo') // ["1", "2"]

上面代码中,查询字符串有两个foo键,getAll返回的数组就有两个成员。

URLSearchParams.sort()

sort()方法对查询字符串里面的键进行排序,规则是按照 Unicode 码点从小到大排列。

该方法没有返回值,或者说返回值是undefined

var params = new URLSearchParams('c=4&a=2&b=3&a=1');
params.sort();
params.toString() // "a=2&a=1&b=3&c=4"

上面代码中,如果有两个同名的键a,它们之间不会排序,而是保留原始的顺序。

关于 URLSearchParams( URL 的查询字符串 ) 更多的性质,详情见MDN - URLSearchParams

也可以参考这篇文章: 浏览器Location 对象,URL 对象,URLSearchParams 对象简析

4. 学习收获或感受

从看工具方法入手学习源码,确实是一个不错的开始。有一些工具方法还能应用到自己的项目中,有一些不常用的先混个面熟。另外,还学到了一些小工具,很不错哦。

  1. 通过 github1s 网页可以很方便地查看源码,类似于在浏览器中打开vscode,体验很棒,不过只能看不能调哦。(使用方法:将github改成github1s,比如:https://github.com/axios/axios ----> https://github1s.com/axios/axios,回车即可)。
  2. 在线编写和调试代码的工具 CodeSandbox ;
  3. vs code 的 code Runner插件