国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Vue項(xiàng)目中axios的原理(詳細(xì)到源碼)

這篇具有很好參考價(jià)值的文章主要介紹了Vue項(xiàng)目中axios的原理(詳細(xì)到源碼)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

Vue項(xiàng)目中axios的原理(詳細(xì)到源碼),vue.js,javascript,前端,前端框架,小程序,微信小程序,react.js



一、axios的使用

關(guān)于axios的基本使用,上篇文章已經(jīng)有所涉及,這里再稍微回顧下:

發(fā)送請(qǐng)求

import axios from 'axios';

axios(config) // 直接傳入配置
axios(url[, config]) // 傳入url和配置
axios[method](url[, option]) // 直接調(diào)用請(qǐng)求方式方法,傳入url和配置
axios[method](url[, data[, option]]) // 直接調(diào)用請(qǐng)求方式方法,傳入data、url和配置
axios.request(option) // 調(diào)用 request 方法

const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力

axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 調(diào)用 all 和傳入 spread 回調(diào)

請(qǐng)求攔截器

axios.interceptors.request.use(function (config) {
    // 這里寫(xiě)發(fā)送請(qǐng)求前處理的代碼
    return config;
}, function (error) {
    // 這里寫(xiě)發(fā)送請(qǐng)求錯(cuò)誤相關(guān)的代碼
    return Promise.reject(error);
});

響應(yīng)攔截器

axios.interceptors.response.use(function (response) {
    // 這里寫(xiě)得到響應(yīng)數(shù)據(jù)后處理的代碼
    return response;
}, function (error) {
    // 這里寫(xiě)得到錯(cuò)誤響應(yīng)處理的代碼
    return Promise.reject(error);
});

取消請(qǐng)求

// 方式一
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('xxxx', {
  cancelToken: source.token
})
// 取消請(qǐng)求 (請(qǐng)求原因是可選的)
source.cancel('主動(dòng)取消請(qǐng)求');

// 方式二
const CancelToken = axios.CancelToken;
let cancel;

axios.get('xxxx', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});
cancel('主動(dòng)取消請(qǐng)求');

二、實(shí)現(xiàn)一個(gè)簡(jiǎn)易版axios

構(gòu)建一個(gè)Axios構(gòu)造函數(shù),核心代碼為request

class Axios {
    constructor() {

    }

    request(config) {
        return new Promise(resolve => {
            const {url = '', method = 'get', data = {}} = config;
            // 發(fā)送ajax請(qǐng)求
            const xhr = new XMLHttpRequest();
            xhr.open(method, url, true);
            xhr.onload = function() {
                console.log(xhr.responseText)
                resolve(xhr.responseText);
            }
            xhr.send(data);
        })
    }
}

導(dǎo)出axios實(shí)例

// 最終導(dǎo)出axios的方法,即實(shí)例的request方法
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// 得到最后的全局變量axios
let axios = CreateAxiosFn();

上述就已經(jīng)能夠?qū)崿F(xiàn)axios({ })這種方式的請(qǐng)求

下面是來(lái)實(shí)現(xiàn)下axios.method()這種形式的請(qǐng)求

// 定義get,post...方法,掛在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
    Axios.prototype[met] = function() {
        console.log('執(zhí)行'+met+'方法');
        // 處理單個(gè)方法
        if (['get', 'delete', 'head', 'options'].includes(met)) { // 2個(gè)參數(shù)(url[, config])
            return this.request({
                method: met,
                url: arguments[0],
                ...arguments[1] || {}
            })
        } else { // 3個(gè)參數(shù)(url[,data[,config]])
            return this.request({
                method: met,
                url: arguments[0],
                data: arguments[1] || {},
                ...arguments[2] || {}
            })
        }

    }
})

Axios.prototype上的方法搬運(yùn)到 request

首先實(shí)現(xiàn)個(gè)工具類(lèi),實(shí)現(xiàn)將b方法混入到a,并且修改this指向

const utils = {
  extend(a,b, context) {
    for(let key in b) {
      if (b.hasOwnProperty(key)) {
        if (typeof b[key] === 'function') {
          a[key] = b[key].bind(context);
        } else {
          a[key] = b[key]
        }
      }
      
    }
  }
}

修改導(dǎo)出的方法

function CreateAxiosFn() {
  let axios = new Axios();
  
  let req = axios.request.bind(axios);
  // 增加代碼
  utils.extend(req, Axios.prototype, axios)
  
  return req;
}

構(gòu)建攔截器的構(gòu)造函數(shù)

class InterceptorsManage {
  constructor() {
    this.handlers = [];
  }

  use(fullfield, rejected) {
    this.handlers.push({
      fullfield,
      rejected
    })
  }
}

實(shí)現(xiàn)axios.interceptors.response.useaxios.interceptors.request.use

class Axios {
    constructor() {
        // 新增代碼
        this.interceptors = {
            request: new InterceptorsManage,
            response: new InterceptorsManage
        }
    }

    request(config) {
 		...
    }
}

執(zhí)行語(yǔ)句axios.interceptors.response.useaxios.interceptors.request.use的時(shí)候,實(shí)現(xiàn)獲取axios實(shí)例上的interceptors對(duì)象,然后再獲取responserequest攔截器,再執(zhí)行對(duì)應(yīng)的攔截器的use方法

Axios上的方法和屬性搬到request過(guò)去

function CreateAxiosFn() {
  let axios = new Axios();
  
  let req = axios.request.bind(axios);
  // 混入方法, 處理axios的request方法,使之擁有g(shù)et,post...方法
  utils.extend(req, Axios.prototype, axios)
  // 新增代碼
  utils.extend(req, axios)
  return req;
}

現(xiàn)在request也有了interceptors對(duì)象,在發(fā)送請(qǐng)求的時(shí)候,會(huì)先獲取request攔截器的handlers的方法來(lái)執(zhí)行

首先將執(zhí)行ajax的請(qǐng)求封裝成一個(gè)方法

request(config) {
    this.sendAjax(config)
}
sendAjax(config){
    return new Promise(resolve => {
        const {url = '', method = 'get', data = {}} = config;
        // 發(fā)送ajax請(qǐng)求
        console.log(config);
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.onload = function() {
            console.log(xhr.responseText)
            resolve(xhr.responseText);
        };
        xhr.send(data);
    })
}

獲得handlers中的回調(diào)

request(config) {
    // 攔截器和請(qǐng)求組裝隊(duì)列
    let chain = [this.sendAjax.bind(this), undefined] // 成對(duì)出現(xiàn)的,失敗回調(diào)暫時(shí)不處理

    // 請(qǐng)求攔截
    this.interceptors.request.handlers.forEach(interceptor => {
        chain.unshift(interceptor.fullfield, interceptor.rejected)
    })

    // 響應(yīng)攔截
    this.interceptors.response.handlers.forEach(interceptor => {
        chain.push(interceptor.fullfield, interceptor.rejected)
    })

    // 執(zhí)行隊(duì)列,每次執(zhí)行一對(duì),并給promise賦最新的值
    let promise = Promise.resolve(config);
    while(chain.length > 0) {
        promise = promise.then(chain.shift(), chain.shift())
    }
    return promise;
}

chains大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']這種形式

這樣就能夠成功實(shí)現(xiàn)一個(gè)簡(jiǎn)易版axios


三、源碼分析

首先看看目錄結(jié)構(gòu)
Vue項(xiàng)目中axios的原理(詳細(xì)到源碼),vue.js,javascript,前端,前端框架,小程序,微信小程序,react.js
axios發(fā)送請(qǐng)求有很多實(shí)現(xiàn)的方法,實(shí)現(xiàn)入口文件為axios.js

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);

  // instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式調(diào)用 
  // Axios.prototype.request 內(nèi)對(duì)第一個(gè)參數(shù)的數(shù)據(jù)類(lèi)型判斷,使我們能夠以 instance(url, option) 方式調(diào)用
  var instance = bind(Axios.prototype.request, context);

  // 把Axios.prototype上的方法擴(kuò)展到instance對(duì)象上,
  // 并指定上下文為context,這樣執(zhí)行Axios原型鏈上的方法時(shí),this會(huì)指向context
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  // 把context對(duì)象上的自身屬性和方法擴(kuò)展到instance上
  // 注:因?yàn)閑xtend內(nèi)部使用的forEach方法對(duì)對(duì)象做for in 遍歷時(shí),只遍歷對(duì)象本身的屬性,而不會(huì)遍歷原型鏈上的屬性
  // 這樣,instance 就有了  defaults、interceptors 屬性。
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported 創(chuàng)建一個(gè)由默認(rèn)配置生成的axios實(shí)例
var axios = createInstance(defaults);

// Factory for creating new instances 擴(kuò)展axios.create工廠函數(shù),內(nèi)部也是 createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

axios.spread = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};
module.exports = axios;

主要核心是 Axios.prototype.request,各種請(qǐng)求方式的調(diào)用實(shí)現(xiàn)都是在 request 內(nèi)部實(shí)現(xiàn)的, 簡(jiǎn)單看下 request 的邏輯

Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  // 判斷 config 參數(shù)是否是 字符串,如果是則認(rèn)為第一個(gè)參數(shù)是 URL,第二個(gè)參數(shù)是真正的config
  if (typeof config === 'string') {
    config = arguments[1] || {};
    // 把 url 放置到 config 對(duì)象中,便于之后的 mergeConfig
    config.url = arguments[0];
  } else {
    // 如果 config 參數(shù)是否是 字符串,則整體都當(dāng)做config
    config = config || {};
  }
  // 合并默認(rèn)配置和傳入的配置
  config = mergeConfig(this.defaults, config);
  // 設(shè)置請(qǐng)求方法
  config.method = config.method ? config.method.toLowerCase() : 'get';
  /*
    something... 此部分會(huì)在后續(xù)攔截器單獨(dú)講述
  */
};

// 在 Axios 原型上掛載 'delete', 'get', 'head', 'options' 且不傳參的請(qǐng)求方法,實(shí)現(xiàn)內(nèi)部也是 request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

// 在 Axios 原型上掛載 'post', 'put', 'patch' 且傳參的請(qǐng)求方法,實(shí)現(xiàn)內(nèi)部同樣也是 request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

request入口參數(shù)為config,可以說(shuō)config貫徹了axios`的一生

axios 中的 config 主要分布在這幾個(gè)地方:

  • 默認(rèn)配置 defaults.js
  • config.method默認(rèn)為 get
  • 調(diào)用 createInstance 方法創(chuàng)建 axios 實(shí)例,傳入的 config
  • 直接或間接調(diào)用 request 方法,傳入的 config
// axios.js
// 創(chuàng)建一個(gè)由默認(rèn)配置生成的axios實(shí)例
var axios = createInstance(defaults);

// 擴(kuò)展axios.create工廠函數(shù),內(nèi)部也是 createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Axios.js
// 合并默認(rèn)配置和傳入的配置
config = mergeConfig(this.defaults, config);
// 設(shè)置請(qǐng)求方法
config.method = config.method ? config.method.toLowerCase() : 'get';

從源碼中,可以看到優(yōu)先級(jí):默認(rèn)配置對(duì)象 default < method:get < Axios 的實(shí)例屬性 this.default < request 參數(shù)

下面重點(diǎn)看看request方法

Axios.prototype.request = function request(config) {
  /*
    先是 mergeConfig ... 等,不再闡述
  */
  // Hook up interceptors middleware 創(chuàng)建攔截器鏈. dispatchRequest 是重中之重,后續(xù)重點(diǎn)
  var chain = [dispatchRequest, undefined];

  // push各個(gè)攔截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能為undefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    // 請(qǐng)求攔截器逆序 注意此處的 forEach 是自定義的攔截器的forEach方法
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    // 響應(yīng)攔截器順序 注意此處的 forEach 是自定義的攔截器的forEach方法
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 初始化一個(gè)promise對(duì)象,狀態(tài)為resolved,接收到的參數(shù)為已經(jīng)處理合并過(guò)的config對(duì)象
  var promise = Promise.resolve(config);

  // 循環(huán)攔截器的鏈
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()); // 每一次向外彈出攔截器
  }
  // 返回 promise
  return promise;
};

攔截器interceptors是在構(gòu)建axios實(shí)例化的屬性

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(), // 請(qǐng)求攔截
    response: new InterceptorManager() // 響應(yīng)攔截
  };
}

InterceptorManager構(gòu)造函數(shù)

// 攔截器的初始化 其實(shí)就是一組鉤子函數(shù)
function InterceptorManager() {
  this.handlers = [];
}

// 調(diào)用攔截器實(shí)例的use時(shí)就是往鉤子函數(shù)中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 攔截器是可以取消的,根據(jù)use的時(shí)候返回的ID,把某一個(gè)攔截器方法置為null
// 不能用 splice 或者 slice 的原因是 刪除之后 id 就會(huì)變化,導(dǎo)致之后的順序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 這就是在 Axios的request方法中 中循環(huán)攔截器的方法 forEach 循環(huán)執(zhí)行鉤子函數(shù)
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
}

請(qǐng)求攔截器方法是被 unshift 到攔截器中,響應(yīng)攔截器是被push到攔截器中的。最終它們會(huì)拼接上一個(gè)叫dispatchRequest的方法被后續(xù)的 promise 順序執(zhí)行

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

// 判斷請(qǐng)求是否已被取消,如果已經(jīng)被取消,拋出已取消
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // 如果包含baseUrl, 并且不是config.url絕對(duì)路徑,組合baseUrl以及config.url
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    // 組合baseURL與url形成完整的請(qǐng)求路徑
    config.url = combineURLs(config.baseURL, config.url);
  }

  config.headers = config.headers || {};

  // 使用/lib/defaults.js中的transformRequest方法,對(duì)config.headers和config.data進(jìn)行格式化
  // 比如將headers中的Accept,Content-Type統(tǒng)一處理成大寫(xiě)
  // 比如如果請(qǐng)求正文是一個(gè)Object會(huì)格式化為JSON字符串,并添加application/json;charset=utf-8的Content-Type
  // 等一系列操作
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 合并不同配置的headers,config.headers的配置優(yōu)先級(jí)更高
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 刪除headers中的method屬性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // 如果config配置了adapter,使用config中配置adapter的替代默認(rèn)的請(qǐng)求方法
  var adapter = config.adapter || defaults.adapter;

  // 使用adapter方法發(fā)起請(qǐng)求(adapter根據(jù)瀏覽器環(huán)境或者Node環(huán)境會(huì)有不同)
  return adapter(config).then(
    // 請(qǐng)求正確返回的回調(diào)
    function onAdapterResolution(response) {
      // 判斷是否以及取消了請(qǐng)求,如果取消了請(qǐng)求拋出以取消
      throwIfCancellationRequested(config);

      // 使用/lib/defaults.js中的transformResponse方法,對(duì)服務(wù)器返回的數(shù)據(jù)進(jìn)行格式化
      // 例如,使用JSON.parse對(duì)響應(yīng)正文進(jìn)行解析
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      );

      return response;
    },
    // 請(qǐng)求失敗的回調(diào)
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);

        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
          );
        }
      }
      return Promise.reject(reason);
    }
  );
};

再來(lái)看看axios是如何實(shí)現(xiàn)取消請(qǐng)求的,實(shí)現(xiàn)文件在CancelToken.js

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }
  // 在 CancelToken 上定義一個(gè) pending 狀態(tài)的 promise ,將 resolve 回調(diào)賦值給外部變量 resolvePromise
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  // 立即執(zhí)行 傳入的 executor函數(shù),將真實(shí)的 cancel 方法通過(guò)參數(shù)傳遞出去。
  // 一旦調(diào)用就執(zhí)行 resolvePromise 即前面的 promise 的 resolve,就更改promise的狀態(tài)為 resolve。
  // 那么xhr中定義的 CancelToken.promise.then方法就會(huì)執(zhí)行, 從而xhr內(nèi)部會(huì)取消請(qǐng)求
  executor(function cancel(message) {
    // 判斷請(qǐng)求是否已經(jīng)取消過(guò),避免多次執(zhí)行
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  // source 方法就是返回了一個(gè) CancelToken 實(shí)例,與直接使用 new CancelToken 是一樣的操作
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // 返回創(chuàng)建的 CancelToken 實(shí)例以及取消方法
  return {
    token: token,
    cancel: cancel
  };
};

實(shí)際上取消請(qǐng)求的操作是在 xhr.js 中也有響應(yīng)的配合的

if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }
        // 取消請(qǐng)求
        request.abort();
        reject(cancel);
    });
}

巧妙的地方在 CancelToken中 executor 函數(shù),通過(guò)resolve函數(shù)的傳遞與執(zhí)行,控制promise的狀態(tài)


小結(jié)

Vue項(xiàng)目中axios的原理(詳細(xì)到源碼),vue.js,javascript,前端,前端框架,小程序,微信小程序,react.js


參考文獻(xiàn)

  • https://juejin.cn/post/6856706569263677447#heading-4
  • https://juejin.cn/post/6844903907500490766
  • https://github.com/axios/axios

希望本文能夠?qū)δ兴鶐椭∪绻腥魏螁?wèn)題或建議,請(qǐng)隨時(shí)在評(píng)論區(qū)留言聯(lián)系 章挨踢(章IT)
謝謝閱讀!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-807307.html

到了這里,關(guān)于Vue項(xiàng)目中axios的原理(詳細(xì)到源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包