• AXIOS源代码重点难点分析


    • 摘要

    vue使用axios进行http通讯,类似jquery/ajax的作用,类似angular http的作用,axios功能强大,使用方便,是一个优秀的http软件,本文旨在分享axios源代码重点难点分析,无意从头到尾详细分析源代码的各个细节。

    • axios的封装

    axios做了复杂深奥的封装,不同于普通的对象/实例方法。

    debug看axios.get()代码是:
    bind.js:
    module.exports = function bind(fn, thisArg) {
    return function wrap() { //axios是这个方法
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
    args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
    };

    再看axios入口文件axios.js代码:
    function createInstance(defaultConfig) {
    var context = new Axios(defaultConfig); //这是axios实例
    var instance = bind(Axios.prototype.request, context); //bind返回wrap方法,而不是axios实例
    // Copy axios.prototype to instance
    utils.extend(instance, Axios.prototype, context); //把Axios静态对象的属性方法复制到instance,由于要改变属性方法中的this指针,因此不能简单复制属性方法,
    而是要写成warp方法,此方法返回fn.apply(context,args),那么调用属性方法时,就是执行wrap方法,就是执行
    fn.apply然后返回,也就是给方法fn加一层封装以便调整指针,否则属性方法就是fn很简单。
    // Copy context to instance
    utils.extend(instance, context); //把axios实例的属性方法复制到instance
    return instance;
    }
    var axios = createInstance(defaults); //defaults对象是缺省配置数据,用var defaults=require('./defaults')获取
    module.exports = axios;

    在bind看fn是:
    Axios.prototype.request
    Axios.prototype[method]

    fn就是Axios静态对象的属性方法:request,get, get方法代码如下:

    utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
    Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, { //this在调用这个方法时已经被调整为Axios实例
    method: method,
    url: url
    }));
    };

    就是执行Axios实例的request方法。

    在extend代码用debug看Axios.prototype有如下属性方法:
    request
    delete
    get
    head
    post
    put
    patch
    defaults
    interceptors

    这些属性方法复制到axios,axios就有这些属性方法,因此可以写axios.get调用其方法,但debug看axios是一个wrap方法,
    是一个构造函数,也是一个对象,是一个特殊的对象,不能new axios()实例化,看axios看不见属性,但看axios.get是有的,
    看axios.abc是没有的。
    因此axios是一个很特殊的构造函数/对象,其属性方法也是特殊构造的,就是wrap()函数,代码就是fn.apply(),因此
    调用axios.get时,确实是调用axios对象的get属性方法,就是调用Axios.prototype.get方法,Axios是原型,axios是一个
    instance,instance继承原型,在调用原型方法时把this调整为原型实例new Axios()。
    这个编程方法很高级,照理说,一般都是new Axios(),然后调用实例的属性方法,很简单,但它不是这样设计,它是设计
    一个特殊的对象axios,再把Axios做为原型复制到axios,那么axios就是Axios原型的一个instance,再把方法中的this调整为
    new Axios实例,这样当调用axios的属性方法时,相当于是调用new Axios实例的属性方法。
    先定义一个标准的原型Axios,再如此创建一个instance(axios),匪夷所思,这样设计是为了层次化,把原型和instance
    分开。

    axios其实就是Axios,只是axios本身和属性方法做了一个封装。

    • axios重点源代码分析


    用循环方法可以看到axios的所有属性名:
    for(var key in axios){
    console.log(key);
    };
    request
    delete
    get
    head
    post
    put
    patch
    defaults
    interceptors
    Axios
    create
    Cancel
    CancelToken
    isCancel
    all
    spread
    default

    因此axios.defaults是访问/修改axios的defaults属性,也是从Axios.prototype复制来的,复制属性/访问属性没有this问题。

    调用axios.get实际上就是调用Axios.prototype.get原型方法,方法中的this代表new Axios实例:

    utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
    Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
    method: method,
    url: url
    }));

    get方法就是调用request方法,传参method:'get'而已,因此axios.get代码实际上是从request代码开始:

    Axios.prototype.request = function request(config) {
    config = utils.merge(defaults, this.defaults, { method: 'get' }, config);
    //axios.defaults会合并到config,config本来只是传入的数据,这之后增加了data,baseUrl等属性

    config.url = combineURLs(config.baseURL, config.url); //拼接url,调用get时如果输入绝对url路径就不用再拼接

    promise = promise.then(http拦截函数/dispatchrequest); //这里面http过程有嵌套promise
    return promise; //for axios.get().then(callback)

    function dispatchRequest(config) {
    config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
    );
    可以看到axios.headers.common或axios.headers["get"]这样写法的都做为header属性配置都会合并到headers,因此像axios.defaults.headers.common["access_token"] = "123456"这种写法结果是一样的。

    return adapter(config).then(function onAdapterResolution(response) {
    return response;
    }, function onAdapterRejection(reason) {
    return Promise.reject(reason);

    function xhrAdapter(config) {
    return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;
    utils.forEach(requestHeaders, function setRequestHeader(val, key) {
    request.setRequestHeader(key, val);

    在adapter底层调setRequestHeader原生方法设置http request header属性,看http报文头是:

    Request Headers:
    Accept:application/json, text/plain, */* //这个属性是axios设置的缺省属性
    Accept-Encoding:gzip, deflate, br
    Accept-Language:zh-CN,zh;q=0.8
    access_token:123456
    Authorization:token 4b26496037665e0c4d308ec682b6e7fb
    test:hello

    其中一个属性是在http.js中设置的global属性(每个http请求都有效):
    axios.defaults.headers["access_token"] = "123456";

    对于特定的http请求也可以传一个json object {}来设置http/header属性,类似jquery ajax写法:
    axios.get(url,{
    headers:{
    test:"hello"
    },
    params:{
    ID:"123456" //由于是get请求,此参数会自动附加在url中以query string形式传递,与直接在url写?ID=123456一样。
    }
    )

    有些属性是浏览器进行http通讯时使用的缺省属性。



    因此http参数配置,一是可以在http.js写global配置,二是可以在调用get方法时直接写一个{属性名:属性值}传进去即可,
    axios约定的属性名可以参考api,也可以直接看原代码,写参数类似jquery ajax,都是用json object形式,比如:
    {
    headers: {'X-Requested-With': 'XMLHttpRequest'},
    params: { //做为query string参数
    ID: 12345
    },
    data: { // post data
    firstName: 'Fred'
    },
    timeout: 1000, //http request timeout可以每次请求时配置,缺省是5s

    }

    调用写法与jquery ajax类似:axios.get(url,{}),原代码中config数据就是传入的{}。


    原代码中有:
    path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^?/, ''),

    这是构造url时使用配置object的params属性来构造?a=b这样的参数。

    axios功能很强,支持upload/download进度处理,cancelToken机制,还支持proxy,一般只有项目级的软件才支持proxy,
    比如ionic项目环境。

    底层adapter有http.js和xhr.js两个,xhr.js没有proxy处理代码,决定用哪个adapter的代码:

    dispatchrequest:
    var adapter = config.adapter || defaults.adapter;

    如果有自定义的adapter,就执行自定义adapter,否则就执行缺省的adapter,缺省adapter如下决定:

    function getDefaultAdapter() {
    var adapter;
    if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
    } else if (typeof process !== 'undefined') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
    }
    return adapter;

    如果是浏览器则执行xhr,不支持proxy,如果在nodejs运行,用http adapter,支持proxy,所以proxy是nodejs的功能。

    在axios基础之上,可以进一步封装,比如fecth就是调用get,但做了一层封装:
    function fetch(url, params = {}) {
    return new Promise((resolve, reject) => {
    axios.get(url, {
    params: params
    })
    .then(response => {
    resolve(response); //因为是new实例时执行回调传递resolve,因此是resove new实例
    })
    .catch(err => {
    // reject(err)
    })

    批量http写法:
    axios.all([getCarousel(), getthemes(), gethotline(), getArea()])
    .then(axios.spread(function(getCarouselData, getthemesData, gethotlineData, getAreaData) {

    里面直接写axios.get(url)或fetch(url)也一样,但加一层封装逻辑上更直观。

    • axios.all()批量执行http的源代码分析

    axios.spread方法代码:
    function spread(callback) {
    return function wrap(arr) {
    return callback.apply(null, arr);
    };

    它是返回一个wrap封装函数,因此实际上就是then(function wrap(arr){return callback.apply(null,arr);}),promise
    会resolve之前promise实例再传递数据执行then里面的函数,传递的数据是数组,函数形参写,,,是可以的,函数代码用
    arguments[]取参数也可以,去掉spread直接写then(funciton(){也是可以的,用arguments[0]可以获取到数组,axios变换
    参数时加了一层[0]。

    关键还是要看all promise如何resolve,涉及到每个http promise状态变化,挺复杂的。

    axios.all代码:
    axios.all = function all(promises) {
    return Promise.all(promises);
    };

    es6-promise:
    function all(entries) {
    return new Enumerator(this, entries).promise;
    }

    function Enumerator(Constructor, input) {
    this.promise = new Constructor(noop); // all返回这个promise实例
    this._result = new Array(this.length);
    this._enumerate();
    fulfill(this.promise, this._result); //在这里resolve返回的promise实例并传递一个数组

    Enumerator.prototype._enumerate = function () {
    this._eachEntry(_input[i], i);

    Enumerator.prototype._eachEntry = function (entry, i) {
    this._settledAt(entry._state, i, entry._result);
    this._willSettleAt(resolve$$(entry), i);
    this._remaining--;
    this._result[i] = entry;

    Enumerator.prototype._settledAt = function (state, i, value) {
    检查处理一个promise,如果完成就更新all promise数组以及计数
    this._remaining--;
    this._result[i] = value;
    if (this._remaining === 0) {
    //如果所有http promise都完成则resolve axios.all返回的promise
    fulfill(promise, this._result);

    Enumerator.prototype._willSettleAt = function (promise, i) {
    //如果http promise没完成,就定阅一个http promise完成事件,相当于写一个http promise.then(callback),当http promise完成时执行callback执行_settledAt更新all promise数组
    subscribe(promise, undefined, function (value) {
    return enumerator._settledAt(FULFILLED, i, value);

    all/spread只是简单的封装,批处理代码都在Enumerator这个对象中,all的底层就是Enumerator。

    因此all检查等待所有http promise完成,获取每个http的response/value,再resovle本身promise,传递数组,执行then里面
    的callback,callback入口参数是数组,按all数组的书写顺序,不是按每个http完成先后顺序,因为http过程是异步的,完成
    顺序是随机的。等待的办法就是用事件机制,用subscriber()方法,相当于写了then,每个http promise原来没写then,all
    给每个http promise写了一个then,每个http promise完成之后更新all数组并判断是否都完成,如果都完成就执行all后面的
    then。


    另外,InterceptorManager就是建一个handlers[],通过调用use可以把自定义的拦截函数存储在里面。

    Axios底层就是调用xmlhttprequest,就是加了封装,这样书写更方便,使用更方便,相当于angular的http封装,webuploader底层也是,但webuploader是一个应用功能,
    不仅仅是把http封装一下。

  • 相关阅读:
    毕业设计每日博客--第一周4
    毕业设计每日博客--第一周3
    python使用chrome driver做简单爬虫--转载于简书
    毕业设计每日博客--第一周2
    毕业设计每日博客--第一周1
    每周总结9
    阅读笔记9--分析模式
    阅读笔记8--分析模式
    pycharm报错ModuleNotFoundError: No module named 'selenium'
    mac pycharm2019.3 安装和激活
  • 原文地址:https://www.cnblogs.com/pzhu1/p/8365705.html
Copyright © 2020-2023  润新知