• 摘要

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. 【Alpha版本】冲刺阶段 - Day7 - 靠泊

    Alpha:指集成了主要功能的第一个试用版本.在这个版本中有些小功能并未实现.事实上很多软件的 Alpha 版本只是在内部使用.给外部用户使用的 Alpha 版本会起一个比较美妙的名字,例如,技术预览 ...

  2. 201621123043《java程序设计》第五周学习总结

    1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 接口. Comparable接口 .Comparator接口.compareTo. 1.2 尝试使用思维导图将这些关键词组织起来 ...

  3. scrapy crawl xmlfeed spider

    from scrapy.spiders import XMLFeedSpider from myxml.items import MyxmlItem class XmlspiderSpider(XML ...

  4. es6+react.js组件入门初探

    React是一个用于构建用户见面的javascript库. React主要用于构建UI,许多人认为React是MVC中的V(视图) React起源于Facebook的内部项目,用来架设Instagra ...

  5. New UWP Community Toolkit - ImageEx

    概述 UWP Community Toolkit  中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解  ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...

  6. EasyUI 中datagrid 分页。

    注释:datagrid分页搞了好几天才完全搞好,网上没完全的资料.明天晚上贴代码. 睡觉.

  7. 剑指offer-反转单词顺序列

    题目描述 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思.例如,"st ...

  8. 使用freemaker 导出word 含多张图片,若无图片则显示文本信息

    1.使用的Microsoft Office 2007,添加一个无边框的表格,并插入一张图片,最后另存为编码utf-8,一开始保存的word xml格式的,图片的base64编码位于文档最后,暂时没有找 ...

  9. 什么是MQTT协议?

    MQTT协议介绍 MQTT协议是什么? MQTT(Message Queuing Telemetry Transport Protocol)的全称是消息队列遥感传输协议的缩写,是一种基于轻量级代理的发 ...

  10. ELK学习总结(4-2)关于导入数据

    用REST API的_bulk来批量插入,可以达到5到10w条每秒 把数据写进json文件,然后再通过批处理,执行文件插入数据: 1.先定义一定格式的json文件,文件不能过大,过大会报错 2.后用c ...