axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。本質上也是對原生XHR的封裝。node
axios.defaults.baseURL = 'http://xxx.com/api';
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios.interceptors.request.use(resolveFn1, rejectFn2); // 添加請求攔截器
axios.interceptors.response.use(resolveFn2, rejectFn2); // 添加響應攔截器
axios('/user?ID=12345').then(() => {
// 請求成功的處理
}, () => {
// 請求異常的處理
}
);
axios.get('/user?ID=12345').then(function (response) {
//
}).catch(function (error) {
//
}).finally(function () {
//
});
複製代碼
加載axios文件後,能夠直接使用axios.get,能夠把axios看做是一個對象,找到axios/lib/axios.js
文件,就能夠直接看到var axiosios
var axios = createInstance(defaults);
複製代碼
defaults是一個對象,一些默認配置值ajax
{
adapter: ƒ xhrAdapter(config)
headers: {
common: { Accept: "application/json, text/plain, */*" },
delete: {},
get: {},
head: {},
patch:{ Content-Type: "application/x-www-form-urlencoded" }
post: { Content-Type: "application/x-www-form-urlencoded" }
put: { Content-Type: "application/x-www-form-urlencoded" }
}
maxContentLength: -1
timeout: 0
transformRequest: [ƒ]
transformResponse: [ƒ]
validateStatus: ƒ validateStatus(status)
xsrfCookieName: "XSRF-TOKEN"
xsrfHeaderName: "X-XSRF-TOKEN"
}
複製代碼
createInstance方法具體實現json
function createInstance(defaultConfig) {
//new Axios的實例,context就是一個對象了
var context = new Axios(defaultConfig);
//instance是一個函數,是Axios.prototype.request方法
var instance = bind(Axios.prototype.request, context);
// 複製Axios.prototype到instance
utils.extend(instance, Axios.prototype, context);
//複製context到instance
utils.extend(instance, context);
return instance;
}
複製代碼
先看看Axios構造函數是什麼,建立了一個怎麼樣的實例。在axios/lib/core/Axios.js
中axios
function Axios(instanceConfig) {
//兩個屬性
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
//原型對象上方法
Axios.prototype.request = function(){...}
Axios.prototype.getUri = function(){...}
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
複製代碼
構造函數Axios.prototype上,終於看到有get方法的定義,get方法最終仍是調的request方法。context是Axios的實例,可是axios卻不是Axios的實例化對象api
那instance又是什麼呢?如何將axios和context對應上呢?promise
接着往下看bind瀏覽器
function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
};
複製代碼
bind返回一個函數wrap,暫且不看裏面得話,咱們就能夠知道instance是一個函數,那麼當instance執行的時候,其實就是執行Axios.proptotype.request,this指向context,說白了bind就是改變了this指向,createInstance函數返回就是這個instance,因此axios就是instance,就是一個函數
bash
function createInstance(defaultConfig) {
//...
// 複製Axios.prototype到instance
utils.extend(instance, Axios.prototype, context);
//複製context到instance
utils.extend(instance, context);
return instance;
}
複製代碼
咱們探探extend究竟作了什麼,找到axios/lib/utils.js
服務器
// extend就是把b的屬性都複製個a
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
//若是屬性的值是一個方法的話,就改變this的指向到thisArg再複製給a
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
複製代碼
instance繼承了Axios.prototype和context的全部的屬性,屬性值是方法的話,裏面的this都是指向context的。instance就是axios。
回到最開始axios.get的調用,其實就是調用Axios.prototype.get。Axios.prototype.get內部最終調用Axios.prototype.request
Axios.prototype.request = function request(config) {
//...
//開始就對config進行判斷,合併默認配置
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
//攔截器相關
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
複製代碼
Axios.prototype.request返回promise,因此能夠鏈式調用axios.get(''/user?ID=12345'').then().catch()。
while循環執行dispatchRequest,繼續看看它究竟作了什麼? 找到axios/lib/core/dispatchRequest.js
文件
function dispatchRequest(config){
// ...
// 開始就對config的 baseUrl, data, headers進行處理
var adapter = config.adapter || defaults.adapter;
// 執行adapter是一個Promise對象,resolve的函數的參數仍是response
// adpater確定是去發送請求了啊
return adapter(config).then(function(response){
// ...
return response
}, function(reason){
// ...
return Promise.reject(reason)
})
}
複製代碼
調用了adapter,config中沒有,看defaults.adapter。進入到axios/lib/defaults.js
文件
var defaults = {
adapter: 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]') {
//node環境
adapter = require('./adapters/http');
}
return adapter;
}
複製代碼
adapter是區分瀏覽器和node環境的,那麼咱們就去axios/lib/adapter/xhr.js
看下瀏覽器環境的,就是封裝了一個promise進行ajax請求服務器。
至此回過頭來再看開始那個get請求,axios.get繼承於Axios.prototype.get,其實就是去執行Axios.prototype.request,返回一個promsie對象,因此可使用then和catch接收返回值和處理錯誤。進行ajax請求的主要函數就是adapter,adapter區分了一下瀏覽器和node環境
爲何不將全部方法在Axios上實現而後返回new Axios呢?
根據源碼可知,axios實例(instance)是對Axios.prototype.request方法包裹了一層函數,主要是爲將Axios.prototype.request內部的this綁定到新建的Axios對象(context)上。而後經過 utils.extend 將內部context和Axios.prototyp的屬性添加到這個Axios.prototype.request方法(intance)上,添加上去的函數也會綁定this到新建的Axios對象(context)上。最終的axios實例上面的方法內部的this指向的都是新建的Axios對象(context),從而使得不一樣axios實例之間隔離了做用域,能夠對每一個axios實例設置不一樣的config。
axios('/user?ID=12345').then(() => {
// 請求成功的處理
}, () => {
// 請求異常的處理
}
);
複製代碼
由於axios內部調用的都是Axios.prototype.request方法,Axios.prototype.request默認請求方法爲get。爲了讓開發者能夠直接axios(config)就能夠發送get請求,而不須要axios.get(config)。若是直接new一個Axios對象是沒法實現這種簡寫的(沒錯,就是爲了少打幾個字)
function request(config) {
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
//合併參數
config = mergeConfig(this.defaults, config);
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get'; //默認調用get方法
}
//...
複製代碼
實際上axios.post、axios.put等全部axios的請求方法內部都是調用Axios.prototype.request