[js] axios为什么可以使用对象和函数两种方式调用?是如何实现的?
[js] axios為什么可以使用對象和函數(shù)兩種方式調(diào)用?是如何實現(xiàn)的?
axios 源碼 初始化
看源碼第一步,先看package.json。一般都會申明 main 主入口文件。
// package.json
{
“name”: “axios”,
“version”: “0.19.0”,
“description”: “Promise based HTTP client for the browser and node.js”,
“main”: “index.js”,
// …
}
復(fù)制代碼
主入口文件
// index.js
module.exports = require(’./lib/axios’);
復(fù)制代碼
4.1 lib/axios.js主文件
axios.js文件 代碼相對比較多。分為三部分展開敘述。
第一部分:引入一些工具函數(shù)utils、Axios構(gòu)造函數(shù)、默認配置defaults等。
第二部分:是生成實例對象 axios、axios.Axios、axios.create等。
第三部分取消相關(guān)API實現(xiàn),還有all、spread、導(dǎo)出等實現(xiàn)。
4.1.1 第一部分
引入一些工具函數(shù)utils、Axios構(gòu)造函數(shù)、默認配置defaults等。
// 第一部分:
// lib/axios
// 嚴格模式
‘use strict’;
// 引入 utils 對象,有很多工具方法。
var utils = require(’./utils’);
// 引入 bind 方法
var bind = require(’./helpers/bind’);
// 核心構(gòu)造函數(shù) Axios
var Axios = require(’./core/Axios’);
// 合并配置方法
var mergeConfig = require(’./core/mergeConfig’);
// 引入默認配置
var defaults = require(’./defaults’);
復(fù)制代碼
4.1.2 第二部分
是生成實例對象 axios、axios.Axios、axios.create等。
/**
- Create an instance of Axios
- @param {Object} defaultConfig The default config for the instance
- @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// new 一個 Axios 生成實例對象
var context = new Axios(defaultConfig);
// bind 返回一個新的 wrap 函數(shù),
// 也就是為什么調(diào)用 axios 是調(diào)用 Axios.prototype.request 函數(shù)的原因
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// 復(fù)制 Axios.prototype 到實例上。
// 也就是為什么 有 axios.get 等別名方法,
// 且調(diào)用的是 Axios.prototype.get 等別名方法。
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// 復(fù)制 context 到 intance 實例
// 也就是為什么默認配置 axios.defaults 和攔截器 axios.interceptors 可以使用的原因
// 其實是new Axios().defaults 和 new Axios().interceptors
utils.extend(instance, context);
// 最后返回實例對象,以上代碼,在上文的圖中都有體現(xiàn)。這時可以仔細看下上圖。
return instance;
}
// Create the default instance to be exported
// 導(dǎo)出 創(chuàng)建默認實例
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// 暴露 Axios class 允許 class 繼承 也就是可以 new axios.Axios()
// 但 axios 文檔中 并沒有提到這個,我們平時也用得少。
axios.Axios = Axios;
// Factory for creating new instances
// 工廠模式 創(chuàng)建新的實例 用戶可以自定義一些參數(shù)
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
復(fù)制代碼
這里簡述下工廠模式。axios.create,也就是用戶不需要知道內(nèi)部是怎么實現(xiàn)的。
舉個生活的例子,我們買手機,不需要知道手機是怎么做的,就是工廠模式。
看完第二部分,里面涉及幾個工具函數(shù),如bind、extend。接下來講述這幾個工具方法。
4.1.3 工具方法之 bind
axios/lib/helpers/bind.js
‘use strict’;
// 返回一個新的函數(shù) wrap
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];
}
// 把 argument 對象放在數(shù)組 args 里
return fn.apply(thisArg, args);
};
};
復(fù)制代碼
傳遞兩個參數(shù)函數(shù)和thisArg指向。
把參數(shù)arguments生成數(shù)組,最后調(diào)用返回參數(shù)結(jié)構(gòu)。
其實現(xiàn)在 apply 支持 arguments這樣的類數(shù)組對象了,不需要手動轉(zhuǎn)數(shù)組。
那么為啥作者要轉(zhuǎn)數(shù)組,為了性能?當(dāng)時不支持?抑或是作者不知道?這就不得而知了。有讀者知道歡迎評論區(qū)告訴筆者呀。
關(guān)于apply、call和bind等不是很熟悉的讀者,可以看筆者的另一個面試官問系列。
面試官問:能否模擬實現(xiàn)JS的bind方法
舉個例子
function fn(){
console.log.apply(console, arguments);
}
fn(1,2,3,4,5,6, ‘若川’);
// 1 2 3 4 5 6 ‘若川’
復(fù)制代碼
4.1.4 工具方法之 utils.extend
axios/lib/utils.js
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;
}
復(fù)制代碼
其實就是遍歷參數(shù) b 對象,復(fù)制到 a 對象上,如果是函數(shù)就是則用 bind 調(diào)用。
4.1.5 工具方法之 utils.forEach
axios/lib/utils.js
遍歷數(shù)組和對象。設(shè)計模式稱之為迭代器模式。很多源碼都有類似這樣的遍歷函數(shù)。比如大家熟知的jQuery $.each。
/**
- @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
// 判斷 null 和 undefined 直接返回
if (obj === null || typeof obj === ‘undefined’) {
return;
}
// Force an array if not already something iterable
// 如果不是對象,放在數(shù)組里。
if (typeof obj !== ‘object’) {
/eslint no-param-reassign:0/
obj = [obj];
}
// 是數(shù)組 則用for 循環(huán),調(diào)用 fn 函數(shù)。參數(shù)類似 Array.prototype.forEach 的前三個參數(shù)。
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 in 遍歷對象,但 for in 會遍歷原型鏈上可遍歷的屬性。
// 所以用 hasOwnProperty 來過濾自身屬性了。
// 其實也可以用Object.keys來遍歷,它不遍歷原型鏈上可遍歷的屬性。
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
復(fù)制代碼
如果對Object相關(guān)的API不熟悉,可以查看筆者之前寫過的一篇文章。JavaScript 對象所有API解析
4.1.6 第三部分
取消相關(guān)API實現(xiàn),還有all、spread、導(dǎo)出等實現(xiàn)。
// Expose Cancel & CancelToken
// 導(dǎo)出 Cancel 和 CancelToken
axios.Cancel = require(’./cancel/Cancel’);
axios.CancelToken = require(’./cancel/CancelToken’);
axios.isCancel = require(’./cancel/isCancel’);
// Expose all/spread
// 導(dǎo)出 all 和 spread API
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require(’./helpers/spread’);
module.exports = axios;
// Allow use of default import syntax in TypeScript
// 也就是可以以下方式引入
// import axios from ‘a(chǎn)xios’;
module.exports.default = axios;
復(fù)制代碼
這里介紹下 spread,取消的API暫時不做分析,后文再詳細分析。
假設(shè)你有這樣的需求。
function f(x, y, z) {}
var args = [1, 2, 3];
f.apply(null, args);
復(fù)制代碼
那么可以用spread方法。用法:
axios.spread(function(x, y, z) {})([1, 2, 3]);
復(fù)制代碼
實現(xiàn)也比較簡單。源碼實現(xiàn):
/**
- @param {Function} callback
- @returns {Function}
*/
module.exports = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
復(fù)制代碼
上文var context = new Axios(defaultConfig);,接下來介紹核心構(gòu)造函數(shù)Axios。
4.2 核心構(gòu)造函數(shù) Axios
axios/lib/core/Axios.js
構(gòu)造函數(shù)Axios。
function Axios(instanceConfig) {
// 默認參數(shù)
this.defaults = instanceConfig;
// 攔截器 請求和響應(yīng)攔截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
復(fù)制代碼
Axios.prototype.request = function(config){
// 省略,這個是核心方法,后文結(jié)合例子詳細描述
// code …
var promise = Promise.resolve(config);
// code …
return promise;
}
// 這是獲取 Uri 的函數(shù),這里省略
Axios.prototype.getUri = function(){}
// 提供一些請求方法的別名
// Provide aliases for supported request methods
// 遍歷執(zhí)行
// 也就是為啥我們可以 axios.get 等別名的方式調(diào)用,而且調(diào)用的是 Axios.prototype.request 方法
// 這個也在上面的 axios 結(jié)構(gòu)圖上有所體現(xiàn)。
utils.forEach([‘delete’, ‘get’, ‘head’, ‘options’], function forEachMethodNoData(method) {
/eslint func-names:0/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach([‘post’, ‘put’, ‘patch’], function forEachMethodWithData(method) {
/eslint func-names:0/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
module.exports = Axios;
復(fù)制代碼
接下來看攔截器部分。
4.3 攔截器管理構(gòu)造函數(shù) InterceptorManager
請求前攔截,和請求后攔截。
在Axios.prototype.request函數(shù)里使用,具體怎么實現(xiàn)的攔截的,后文配合例子詳細講述。
axios github 倉庫 攔截器文檔
如何使用:
// Add a request interceptor
// 添加請求前攔截器
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
// 添加請求后攔截器
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
復(fù)制代碼
如果想把攔截器,可以用eject方法。
const myInterceptor = axios.interceptors.request.use(function () {/…/});
axios.interceptors.request.eject(myInterceptor);
復(fù)制代碼
攔截器也可以添加自定義的實例上。
const instance = axios.create();
instance.interceptors.request.use(function () {/…/});
復(fù)制代碼
源碼實現(xiàn):
構(gòu)造函數(shù),handles 用于存儲攔截器函數(shù)。
function InterceptorManager() {
this.handlers = [];
}
復(fù)制代碼
接下來聲明了三個方法:使用、移除、遍歷。
4.3.1 InterceptorManager.prototype.use 使用
傳遞兩個函數(shù)作為參數(shù),數(shù)組中的一項存儲的是{fulfilled: function(){}, rejected: function(){}}。返回數(shù)字 ID,用于移除攔截器。
/**
- @param {Function} fulfilled The function to handle then for a Promise
- @param {Function} rejected The function to handle reject for a Promise
- @return {Number} 返回ID 是為了用 eject 移除
/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
復(fù)制代碼
4.3.2 InterceptorManager.prototype.eject 移除
根據(jù) use 返回的 ID 移除 攔截器。
/* - @param {Number} id The ID that was returned by use
/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
復(fù)制代碼
有點類似定時器setTimeout 和 setInterval,返回值是id。用clearTimeout 和clearInterval來清除定時器。
// 提一下 定時器回調(diào)函數(shù)是可以傳參的,返回值 timer 是數(shù)字
var timer = setInterval((name) => {
console.log(name);
}, 1000, ‘若川’);
console.log(timer); // 數(shù)字 ID
// 在控制臺等會再輸入執(zhí)行這句,定時器就被清除了
clearInterval(timer);
復(fù)制代碼
4.3.3 InterceptorManager.prototype.forEach 遍歷
遍歷執(zhí)行所有攔截器,傳遞一個回調(diào)函數(shù)(每一個攔截器函數(shù)作為參數(shù))調(diào)用,被移除的一項是null,所以不會執(zhí)行,也就達到了移除的效果。
/* - @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
復(fù)制代碼
上文敘述的調(diào)試時運行npm start 是用axios/sandbox/client.html路徑的文件作為示例的,讀者可以自行調(diào)試。
以下是一段這個文件中的代碼。
axios(options)
.then(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
})
.catch(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
});
。
個人簡介
我是歌謠,歡迎和大家一起交流前后端知識。放棄很容易,
但堅持一定很酷。歡迎大家一起討論
主目錄
與歌謠一起通關(guān)前端面試題
總結(jié)
以上是生活随笔為你收集整理的[js] axios为什么可以使用对象和函数两种方式调用?是如何实现的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mac和PC在工作中管理的对比(1)
- 下一篇: Excel常用函数+数据透视表