axios
是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。這裏將會從功能出發,分析源碼,深刻了解 axios 是怎麼實現這些功能的。node
IDE: WebStorm
Git地址: https://github.com/cookhot/ax... 注意analysis
分支
中文文檔: https://www.kancloud.cn/yunye...ios
項目的入口是axios.js
, 當axios
在被引入項目中的時候,導入的實際上是一個方法,能夠直接調用此方法發起請求。git
import axios from './axios' console.log(typeof axios); // function axios({ url: 'http://localhost:8088/index' }).then((res) => { console.log(res) })
axios.jsgithub
'use strict'; var utils = require('./utils'); var bind = require('./helpers/bind'); var Axios = require('./core/Axios'); var defaults = require('./defaults'); /** * 建立 Axios 的一個實例 * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * @return {Axios} A new instance of Axios */ function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // instance 是一個方法, 實際上就是 Axios.prorotype.request, 方法的 this => context var instance = bind(Axios.prototype.request, context); // 把 Axios 原型上面的屬性(方法)複製到 instance 上面,保證被複制的方法中 this => context // 注意 utils.extend 和 utils.merge的區別,二者是不一樣的 utils.extend(instance, Axios.prototype, context); // Copy context to instance // context 上面的屬性都複製到 instance,context.defaults 和 context.interceptors 經過instance可以訪問 utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults); // Expose Axios class to allow class inheritance axios.Axios = Axios; // Factory for creating new instances // 建立 Axios 實例 axios.create = function create(instanceConfig) { // instanceConfig 是開發者提供的配置屬性,將會和 Axios 提供的默認配置屬性合併, // 造成的新的配置屬性將會是實例請求的默認屬性 (很經常使用的設計方法) return createInstance(utils.merge(defaults, instanceConfig)); }; // Expose Cancel & CancelToken // 請求取消 axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); // 輸出Axios module.exports = axios; // Allow use of default import syntax in TypeScript module.exports.default = axios;
從上面的源碼中,可以看到axios
其實就是調用的Axios.prototype.request
方法,爲了防止在運行時候this指向異常,顯示的綁定上了context。ajax
// ...... bind 的實現 module.exports = function bind(fn, thisArg) { // 使用閉包 和 apply 改變 fn 的 this 指向 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); }; };
爲了可以讓開發人員更好的調用get、post、...... 等等方法, 因而把Axios.prototype
上面的方法都複製到axios上面。 相應的爲了防止這些方法中this指向異常,也顯示的綁定context, 具體的實現邏輯請看下面 ⤵️ 對象的複製。 後面的 utils.extend(instance, context)
這行代碼是爲了幫助咱們可以經過axios 訪問到 context 上面的屬性, context裏面包含攔截器(interceptors)以及配置屬性值(defaults)。axios
axios提供了兩種的方式來處理對象的合併, 分別是 merge 與 extend。代碼被放在utils.js數組
// utils.js // ....... /** * 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); } } } } /** * 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 (typeof result[key] === 'object' && typeof val === 'object') { result[key] = merge(result[key], val); } else { result[key] = val; } } for (var i = 0, l = arguments.length; i < l; i++) { forEach(arguments[i], assignValue); } return result; } /** * Extends object a by mutably adding to it the properties of object b. * * 這裏考慮了複製函數時候的 this 指向問題,設計的很好,之後能夠借鑑 * @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; }
merge
相似於咱們常常使用的對象淺拷貝,可是又不全是淺拷貝。在拷貝的時候,發現進行拷貝的兩個屬性都是都是對象的時候,就對此屬性對象中子屬性再進行在複製。用於防止前面一個屬性對象中的子屬性值被全覆蓋掉。extend
也是對象的淺拷貝,不過在拷貝方法的時候,會顯示指定方法的this,用於防止this指向異常。promise
axios建立請求後會返回一個Promise的實例,Promise.all
所返回的promise實例會在傳入的promise實例狀態都發生變化,才變動狀態。因此 axios.all
其實就是調用Promise.all
。瀏覽器
axios.all = function all(promises) { return Promise.all(promises); };
cancel 這裏暫時不討論,後面經過結合 XMLHttpRequest 與 node 的 http 會說的明白的更加清楚。閉包
在看完axios.js
之後,就須要開始瞭解Axios
構造函數的實現了。
Axios.js
'use strict'; var defaults = require('./../defaults'); var utils = require('./../utils'); var InterceptorManager = require('./InterceptorManager'); var dispatchRequest = require('./dispatchRequest'); /** * Create a new instance of Axios * * @param {Object} instanceConfig The default config for the instance */ function Axios(instanceConfig) { // instanceConfig => 建立對象的設置的默認值 // Axios 中 defaults 分爲三個層次, Axios 默認的defaults < 建立實例傳入的defaults < 調用方法時候傳入的defaults // 我的感受使用 this.defaults = utils.merge(defaults, instanceConfig) 會更好,當後面使用request發起請求的時候,代碼變化以下: /* config = utils.merge(defaults, this.defaults, config); 老代碼 config = utils.merge(this.defaults, config); // 新代碼 */ this.defaults = instanceConfig; // 攔截器 this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } /** * Dispatch a request * * @param {Object} config The config specific for this request (merged with this.defaults) */ Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API // 重載 request(url, config) // 能夠支持request (config) 也能夠支持 request(url, config) if (typeof config === 'string') { config = utils.merge({ url: arguments[0] }, arguments[1]); } config = utils.merge(defaults, this.defaults, config); config.method = config.method.toLowerCase(); // Hook up interceptors middleware // 攔截器設計處理 // chain 是一個數組 var chain = [dispatchRequest, undefined]; // promise 是調用頭,狀態已經改變爲 resolved var promise = Promise.resolve(config); // 使用 use 添加 fulfilled 與 rejected 添加到隊列中 // 添加 request 攔截函數的時候使用的是unshift, 這樣會致使 use 後添加的先執行,先添加的後執行 /* axios.interceptors.request.use(function resolve(config) { console.log("1"); }); axios.interceptors.request.use(function resolve(config) { console.log("2") }) // 結果 2 1 */ // 考慮到後面 是使用 promise的鏈式調用, 因此在 攔截器的回調方法中 必需要返回一個 config 對象 // 若是不返回 config, 會致使後續請求執行異常 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // response 使用的push 添加 攔截函數,這裏是添加先執行,後添加後執行 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // promise 的初始化狀態就是 resolved,這裏造成了promise調用鏈,執行流程過程以下 // chain [fulfilled, rejected, ... dispatchRequest, undefined ....,fulfilled, rejected] // 這裏補充一下 fulfilled, rejected 都是確定是成對出現的, 具體緣由可看 InterceptorManager.prototype.use // promise.then(undefined, undefined) 中當傳遞的不是function時,會發生值穿。也就是說 use 中能夠傳入非function, // 或者傳入單個function while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; // Provide aliases for supported request methods // 複用request 實現了 delete, get, head, options 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 })); }; }); // 複用request 實現了 post, put, patch 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;
Axios.js
主要處理了兩個部分,複用請求方法、實現攔截器。
當咱們使用 Axios 的實例去發送請求,使用的方法get、post等都是複用了request方法,在request方法中經過 arguments 獲取傳入的參數,實現了傳入參數的重載。
攔截器是axios的一大特點,它的實現原理其實不復雜,核心就是promise的鏈式調用。
原理能夠參考下圖:
而後附上InterceptorManager.js
的源碼,可是我的以爲這裏沒有什麼好說的,其實就是對一個數組的操做。
'use strict'; var utils = require('./../utils'); function InterceptorManager() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected) { // fulfilled => 成功方法 // rejected => 失敗方法 this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; }; /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` */ // 把數組中 對象設置爲 null InterceptorManager.prototype.reject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @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); } }); }; module.exports = InterceptorManager;
若是你看完上面的源碼解讀,可以清楚的明白下面這段代碼執行順序,那麼就說明你掌握axios攔截器的實現了
// 攔截器的執行順序 /* axios.interceptors.request.use(function resolve(config) { console.log("request"); return config; }); axios.interceptors.response.use(function resolve(res) { console.log('response') return res }); axios.get('http://localhost:3000/index').then(function resolve(res) { console.log('ajax'); return res }).then(function(){ console.log('end') }) */
第一篇總算是寫完了,後續還有關於axios
3篇的源碼解讀,若是你們以爲寫的還行的話,麻煩給一個贊?,鼓勵鼓勵,謝謝了