axios中的那些天才程式碼!看完我實力大漲!

林恒發表於2024-07-29

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

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包含defaultsinterceptors屬性,其物件原型__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);

首先,requset 是Axios原型物件上的方法,其方法內部的this指向的是其例項化物件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重新指向其例項化物件。

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

axios中的那些天才程式碼!看完我實力大漲!

相關文章