• 摘要

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. unittest自动化使用HTMLTestRunner的中文编码问题

    1.使用unittest自动化测试框架,使用HTMLTestRunner生成测试报告,中文乱码问题! 如图 2.解决方法: 第一步:先在自己的测试脚本中添加 import sys reload(sys ...

  2. ssh框架-Struts2(一)

    Struts2 概述 用我们自己的话来说: struts是web层框架, 相当于Servlet 作用: 1. 获得请求参数 2. 调用业务 3. 分发转向 常用的WEB层框架 Struts2入门 1. ...

  3. 从PRISM开始学WPF

    我最近打算学习WPF ,在寻找MVVM框架的时候发现了PRISM,在此之前还从一些博客上了解了其他的MVVM框架,比如浅谈WPF中的MVVM框架--MVVMFoundation 中提到的MVVMFou ...

  4. Java 密码学算法

    Java 密码学算法 候捷老师在< 深入浅出MFC 2e(电子版)>中引用林语堂先生的一句话: 只用一样东西,不明白它的道理,实在不高明 只知道How,不知道Why,出了一点小问题时就无能 ...

  5. django搭建web (三) admin.py -- 待续

    demo 关于模型myQuestion,myAnswer将在后述博客提及 # -*- coding: utf-8 -*- from __future__ import unicode_literals ...

  6. react基础篇入门组件

    讲述一下React: 1.声明式设计-React采用声明范式,可以轻松描述应用 2.高效-React通过DOM模型,最大限度的减少dom的交互 3.灵活-React可以与已知的库或框架很好的配合 4. ...

  7. 08-TypeScript中的类

    类的概念通常是在后端开发中实现的思想,比如C#.C++或Java,传统的JavaScript开发通过使用原型模式来模拟类的功能.在TypeScript中,天生就是支持类 的,可以让前端的开发更加具有面 ...

  8. JAVAEE——BOS物流项目09:业务受理需求分析、创建表、实现自动分单、数据表格编辑功能使用方法和工作单快速录入

    1 学习计划 1.业务受理需求分析 n 业务通知单 n 工单 n 工作单 2.创建业务受理环节的数据表 n 业务通知单 n 工单 n 工作单 3.实现业务受理自动分单 n 在CRM服务端扩展方法根据手 ...

  9. 新概念英语(1-13)A new dress

    What colour is Anna's hat? A:What colour is your new dress? B:It's green.Come upstairs and see it. A ...

  10. C# bootstrap之表格动态绑定值

    这段时间研究了下bootstrap,打算从表格开始学习,实现动态绑定值,在网上找了挺多例子,但是很少有写全的,要不就太复杂,实现效果后总结一下,直接拷贝过去可以用. 第一步:先去官网上下载bootst ...