【axios源码阅读】 0 axios的基本结构


交接的项目刚好用到了axios,来看一下axios的源码,先贴一下核心文件的部分代码

function createInstance(defaultConfig) {
    var context = new Axios(defaultConfig);
    var instance = bind(Axios.prototype.request, context);
    utils.extend(instance, Axios.prototype, context);
    utils.extend(instance, context);
    return instance;
}

var axios = createInstance(defaults);

axios.Axios = Axios;

axios.create = function create(instanceConfig) {
    return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

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

axios.spread = require('./helpers/spread');

我们用的axios就是createInstance的返回值,返回值是一个函数,传入的defauts是默认的config,包括header,contentType等。

Axios是一个class,他的prototype上的方法很重要,比如发起请求等。createInstance内通过utils.extends将Axios.prototype的方法拷贝到axios函数上。

axios.create就是createInstance调用,优势在于传入自定义的config增强配置

最后就是spread,all等方法,这两个方法实际上功能相同,实现略有区别。

概括一下axios的关系

axios

createInstance

createInstance源码如下

function createInstance(defaultConfig) {
    var context = new Axios(defaultConfig);
    var instance = bind(Axios.prototype.request, context);
    utils.extend(instance, Axios.prototype, context);
    utils.extend(instance, context);
    return instance;
}

大概过程就是创建一个contenxt,然后让request的this指向context,然后使用extends增强instance函数。

首先看一下defaultConfig

var defaults = {
  adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if () {return data;}
    if () {}
    if () {}
    if () {}
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

  timeout: 0,

  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,

  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

defaults.headers = {
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

defaultConfig包含的是发起请求的基本配置,下面仔细看一下每一项

defaultConfig -> getDefaultAdapter

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    adapter = require('./adapters/http');
  }
  return adapter;
}

getDefaultAdapter根据不同的环境(node or navigator)返回不同的adapter对象,adapter用于发起ajax请求

看一下adapter对象的结构

function xhrAdapter(config){
    return new Promise(...)
}

xhrAdapter函数接收一个config对象,返回一下promise

if (utils.isFormData(requestData)) {
    delete requestHeaders['Content-Type']; // Let the browser set it
}

这里判断一下data的类型,如果是formData就交给浏览器自己设置

if (config.auth) {
    var username = config.auth.username || '';
    var password = config.auth.password || '';
    requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}

验证一下config.auth属性,决定是否进行http标准验证。

request.onreadystatechange = function handleLoad() {

    // readyState == 4说明数据接收完成
    if (!request || request.readyState !== 4) {
        return;
    }

    // request为0可能url不存在,也可能是文件传输协议,因此要区分
    if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
    }

    //getAllResponseHeaders返回的包含header的字符串,parseHeaders返回解析后的对象
    var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;

    //根据responseType获取响应数据
    var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;

    var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
    };
    
    //settle内部调用resolve,改变promise状态返回response
    settle(resolve, reject, response);
    request = null;
};

总结一下onreadystatechange的功能

  1. 区分url不存在还是file协议
  2. 解析headers
  3. 获取data
  4. 构建response对象
  5. settle内部调用resolve,改变promise状态
if (utils.isStandardBrowserEnv()) {
    var cookies = require('./../helpers/cookies');
    var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined;

    if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
    }
}

在request.send前,这里用于阻止xsrf攻击。根据xsrfName读取特定的cookie字段,请求时携带交给后端验证

总结一下xhrAdapter的作用:

  1. 构建请求的promise
  2. 设置contentType
  3. http 基本验证
  4. 构建xhr对象及其相关回调

也就是说ajax的核心操作在xhrAdapter内完成

defaultConfig内其他的配置比较简单,timeout设置允许超时的时间,xsrf相关的字段用于防止攻击。

defaults

create

axios.create = function create(instanceConfig) {
    return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

内部调用的就是createInstance,mergeConfig会将instanceConfig的属性添加到axios.defaults上

到这基本框架就介绍完毕了,下一篇应该会写axios发起请求的流程。


Author: Maple
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Maple !
  TOC