🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
axios的兩種呼叫方式
經常調介面的同學一定非常熟悉aixos下面的兩種使用方式:
- axios(config)
// 配置式請求 axios({ method: 'post', url: '/user/12345', });
- axios.post(url, config)
// 簡潔的寫法 axios.post('/user/12345')
不知道各位大佬有沒有思考過這樣的問題:
axios到底是個什麼東西?我們為什麼可以使用這兩種方式請求介面呢?axios是怎麼設計的?
axios原理簡析
為了搞明白上面的問題,我們先按照傳統思路仿照axios原始碼實現一個簡單的axios。
手寫一個簡單的axios
建立一個建構函式
function Axios(config){ this.defaults = config; // 配置物件 this.interceptors = { // 攔截器物件 request:{}, response:{} } }
上面的程式碼中,我們實現了一個基本的Axios類,但它還不具備任何功能。我們現在給它新增功能。
原型上新增方法
Axios.prototype.request = function(config){ console.log('傳送Ajax 請求 type:' +config.method) } Axios.prototype.get = function(){ return this.request({method:'GET'}) } Axios.prototype.post = function(){ return this.request({method: 'POST'}) }
上面的程式碼中,我們在request屬性上建立了一個通用的介面請求方法,get和post實際都呼叫了request,但內部傳遞了不同的引數,這和axios(config)、axios.post()有異曲同工之妙。
參考aixos的用法, 現在,我們需要建立例項物件
let aixos = new Axios(config)
建立後的axios包含defaults
和interceptors
屬性,其物件原型__proto__
上(指向Axios的prototype)包含request、get及post方法,因此,我們現在可以使用aixos.post()
的方法模擬呼叫介面了。
但注意,此時aixos只是一個例項物件,不是一個函式!我們似乎也沒辦法做到改造程式碼使用aixos(config)
的形式呼叫介面!
aixos是如何實現的呢?
aixos中的天才想法
為了即能使用axios(config)又能使用axios.get(),axios的核心偽邏輯如下
function Axios(config){ this.defaults = config; // 配置物件 this.interceptors = { // 攔截器物件 request:{}, response:{} } } Axios.prototype.request = function(config){ console.log('傳送Ajax 請求 type:' +config.method) } Axios.prototype.get = function(){ return this.request({method:'GET'}) } Axios.prototype.post = function(){ return this.request({method: 'POST'}) } function createInstance(config) { //注意instance是函式 const instance = Axios.prototype.request; instance.get = Axios.prototype.get instance.post = Axios.prototype.post return instance; } let axios = createInstance();
透過上述的虛擬碼,我們可以知道axios是createInstance()函式的返回值instance。
- instance 是一個函式,因此,axios也是一個函式,可以使用axios(config);
- instance也是一個物件(js萬物皆物件),其原型上有get方法和post方法,因此,我們可以使用axios.post()。
我們看看aixos的原始碼
aixos的原始碼實現
function createInstance(config) { //例項化一個物件 var context = new Axios(config); //但是不能直接當函式使用 var instance = Axios.prototype.request.bind(context); //instance 是一個函式,並且可以 instance({}), //將Axios.prototype 物件中的方法新增到instance函式中,讓instance擁有get、post、request等方法屬性 Object.keys(Axios.prototype).forEach(key => { // console.log(key); //修改this指向context instance[key] = Axios.prototype[key].bind(context); }) //總結一下,到此instance自身即相當於Axios原型的request方法, //然後又給instance的屬性新增了上了Axios原型的request、get、post方法屬性 //然後呼叫instance自身或instance的方法屬性時,修改了this指向context這個Axios例項物件 //為instance函式物件新增屬性 default 與 intercetors Object.keys(context).forEach(key => { instance[key] = context[key]; }) return instance; }
可以說,上面的程式碼真的寫的精妙絕倫啊!
注意這裡,為什麼要修改this的指向
var instance = Axios.prototype.request.bind(context);
Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } config = mergeConfig(this.defaults, config); // Set config.method if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); } else { config.method = 'get'; } // Hook up interceptors middleware var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; };
因此,如果我們直接使用Axios.prototype.request()
就會出現問題,因為這事reques方法內部的this會指向錯誤,導致函式不能執行,因此,我們必須將this重新指向其例項化物件。
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。