• 摘要

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封装一下。

AXIOS源代码重点难点分析的更多相关文章

  1. es6-promise源代码重点难点分析

    摘要 vue和axios都可以使用es6-promise来实现f1().then(f2).then(f3)这样的连写形式,es6-promise其实现代浏览器已经支持,无需加载外部文件.由于promi ...

  2. vue 2.0 路由切换以及组件缓存源代码重点难点分析

    摘要 关于vue 2.0源代码分析,已经有不少文档分析功能代码段比如watcher,history,vnode等,但没有一个是分析重点难点的,没有一个是分析大命题的,比如执行router.push之后 ...

  3. react源代码重点难点分析

    网上已经有不少react源码分析文档,但都是分析主流程和主要功能函数,没有一个是从reactDOM.render()入口开始分析源码把流程走通尤其是把复杂重要的细节环节走通直到把组件template编 ...

  4. apache 重点难点

    apache 重点难点 在于难以理解其工作原理,因为它是c 写的:其模块众多:功能强大而复杂. 其配置也是格式不齐, 比如一下子是 key value , 一下子就成了 xml. 转载: http:/ ...

  5. Android开发重点难点1:RelativeLayout(相对布局)详解

    前言 啦啦啦~博主又推出了一个新的系列啦~ 之前的Android开发系列主要以完成实验的过程为主,经常会综合许多知识来写,所以难免会有知识点的交杂,给人一种混乱的感觉. 所以博主推出“重点难点”系列, ...

  6. 第213天:12个HTML和CSS必须知道的重点难点问题

    12个HTML和CSS必须知道的重点难点问题 这12个问题,基本上就是HTML和CSS基础中的重点个难点了,也是必须要弄清楚的基本问题,其中定位的绝对定位和相对定位到底相对什么定位?这个还是容易被忽视 ...

  7. 10个HTML和CSS必须知道的重点难点问题

    前端日刊 登录 10个HTML和CSS必须知道的重点难点问题 2018-02-26 阅读 2982 收藏 6 原链:segmentfault.com 分享到:   前端必备图书<深入浅出Node ...

  8. 从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么

    导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据.选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下.下面我就用一个小例子来模拟. ...

  9. 从Handler+Message+Looper源代码带你分析Android系统的消息处理机制

    PS一句:不得不说CSDN同步做的非常烂.还得我花了近1个小时恢复这篇博客. 引言 [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] 作为A ...

随机推荐

  1. java Classpath 的解读

    在了解java的classpath之前先来看看java的运行机制  1.首先是编译,将.java文件编译成虚拟机认识的二进制文件.这个过程需要的命令是javac  可以在jdk的bin目录中找到,ja ...

  2. Beta 第五天

    今天遇到的困难: 前端大部分代码由我们放逐的组员完成,这影响到了我们解决"Fragment碎片刷新时总产生的固定位置"的进程,很难找到源码对应 新加入的成员对界面代码不熟悉. 我们 ...

  3. alpha-咸鱼冲刺day2

    一,合照 emmmmm.自然是没有的. 二,项目燃尽图 三,项目进展 今天并没有什么进展,弄了好久好像也只研究出怎么把JS的功能块插入进去.html的信息提交这些还不知道要怎么弄. 四,问题困难 日常 ...

  4. 20162328蔡文琛week05

    学号 20162328 <程序设计与数据结构>第X周学习总结 教材学习内容总结 面向对象程序设计的核心是类的定义,它代表定义了状态和行为的对象. 变量的作用域依赖于变量声明的位置,作用域决 ...

  5. 浅谈 ThreadLocal

    有时,你希望将每个线程数据(如用户ID)与线程关联起来.尽管可以使用局部变量来完成此任务,但只能在本地变量存在时才这样做.也可以使用一个实例属性来保存这些数据,但是这样就必须处理线程同步问题.幸运的是 ...

  6. pygame事件之——控制物体(飞机)的移动

    近来想用pygame做做游戏,在 xishui 大神的目光博客中学了学这东西,就上一段自己写的飞机大战的代码,主要是对键盘控制飞机的移动做了相关的优化 # -*- coding: utf-8 -*- ...

  7. Spring Security入门(3-7)Spring Security处理页面的ajax请求

  8. Homebrew update error not work on OSX

    brew update 错误是这样的 chown: /usr/local: Operation not permitted 然后网上osx 10.11, 10.12的解决方法这样的 The probl ...

  9. ribbon 详解

    ribbon 详解 1. 顶层架构 2. 简单的示例:使用ResourceTemplate方式 @Test public void testGroup(){ HttpResourceGroup htt ...

  10. Cookie、Session登陆验证相关介绍和用法

    一.Cookie和Session 首先.HTTP协议是无状态的:所谓的无状态是指每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应直接影响,也不会直接 ...