├── /dist/ # 項目輸出目錄
├── /lib/ # 項目源碼目錄
│ ├── /cancel/ # 定義取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主類
│ │ ├── dispatchRequest.js # 用來調用http請求適配器方法發送請求
│ │ ├── InterceptorManager.js # 攔截器構造函數
│ │ └── settle.js # 根據http響應狀態,改變Promise的狀態
│ ├── /helpers/ # 一些輔助方法
│ ├── /adapters/ # 定義請求的適配器 xhr、http
│ │ ├── http.js # 實現http適配器
│ │ └── xhr.js # 實現xhr適配器
│ ├── axios.js # 對外暴露接口
│ ├── defaults.js # 默認配置
│ └── utils.js # 公用工具
├── package.json # 項目信息
├── index.d.ts # 配置TypeScript的聲明文件
└── index.js # 入口文件
複製代碼
在接下來咱們不在單獨的介紹 工具函數的使用, 咱們在講解到源碼的時候在逐步理解,固然我仍是建議看源碼以前能把工具函數都理解透纔好容易理解javascript
做爲axios項目的入口文件,咱們先來看下axios.js的源碼 可以實現axios的多種使用方式的核心是 createInstance 方法:java
bind 方法node
主要是 apply 的 使用 能夠看這篇文章ios
module.exports = function bind(fn, thisArg) {
// 返回的是一個函數
return function wrap() {
// args 其實就是 參數的集合
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// 改變 this 的指向 能夠理解成 fn 裏邊的 this 是 thisArg
return fn.apply(thisArg, args);
};
};
複製代碼
forEach 方法json
function forEach(obj, fn) {
// 若是沒值 直接返回
if (obj === null || typeof obj === 'undefined') {
return;
}
// 若是尚未可寫的東西,就強制一個數組
if (typeof obj !== 'object') {
obj = [obj];
}
if (isArray(obj)) {
// 若是是數組
for (var i = 0, l = obj.length; i < l; i++) {
// 這裏用到了call 參數 null 說明不改變 this 的指向
fn.call(null, obj[i], i, obj);
}
} else {
// 若是是對象
for (var key in obj) {
// 自身的屬性不包含原型上的
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// 傳參的時候至關於 把 key val 單作參數調用
fn.call(null, obj[key], key, obj);
}
}
}
}
複製代碼
extend 方法axios
我腦瓜子比較笨, 在這裏用到了 bind 方法 我花了很長時間理解 爲何要這麼寫數組
function extend(a, b, thisArg) {
// 循環 B 把 B 的 key val 進行回調的形式 賦給 A
// 合併對象 把 B 的屬性給 A
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
// 注意 是函數的時候 調用 bind
a[key] = bind(val, thisArg);
} else {
// 把 B 的屬性給 A
a[key] = val;
}
});
return a;
}
複製代碼
createInstance這個方法的 倒數第二行 用到了 extend 方法 可是 extend 返回的是 Function 因此 createInstance這個方法的 return 也是一個 Function ,這個Function還會有Axios.prototype上的每一個方法做爲靜態方法,且這些方法的上下文都是指向同一個對象。promise
// 首先建立 Axios 構造函數
function Axios(instanceConfig) {
this.defaults = instanceConfig;
// interceptors 攔截器
this.interceptors = {
request: function() {},
response: function () {}
};
}
// 原型上的 request 方法
Axios.prototype.request = function request(config) {
console.log(this)
}
// 原型上的 get post... 方法
Axios.prototype.get = function request(config) {
console.log(this)
}
// 建立
function createInstance(defaultConfig) {
var context = new Axios({
name: 'wang'
});
}
// 若是到這 咱們看 request 方法中的 this 是 指向了 Axios.prototype
// 可是咱們想用 構造函數的 this 呢
function createInstance(defaultConfig) {
var context = new Axios({
name: 'wang'
});
// 在調用 bind 方法以後 request 中的 this 指向了 實例
// 並且是在執行的時候改變的
var instance = bind(Axios.prototype.request, context);
}
// 若是看到這裏 咱們應該明白爲何 使用 bind 這個函數了
複製代碼
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// 這一行 就是 在調用 createInstance 這個方法的時候 return 其實 Axios.prototype.request 這個方法
var instance = bind(Axios.prototype.request, context);
// instance 合併 原型上的方法,並且原型上全部的方法的this 都指向 實例
utils.extend(instance, Axios.prototype, context);
// 把實例上的 屬性 賦值給 instance
// 實際上是new Axios().defaults 和 new Axios().interceptors
// 也就是爲何默認配置 axios.defaults 和攔截器 axios.interceptors 能夠使用的緣由
utils.extend(instance, context);
return instance;
}
複製代碼
首先咱們看一下這個方法都幹了什麼瀏覽器
我先把 攔截器的代碼註釋給寫一下, 若是沒有明白看我下邊寫的小栗子 就能完全明白攔截器app
Axios.prototype.request = function request(config) {
// 判斷 config 參數是不是 字符串,若是是則認爲第一個參數是 URL,第二個參數是真正的config
if (typeof config === 'string') {
config = arguments[1] || {};
// 把 url 放置到 config 對象中,便於以後的 mergeConfig
config.url = arguments[0];
} else {
// 若是 config 參數是不是 字符串,則總體都當作config
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();
// 默認的也沒有就用 get 方法
} else {
config.method = 'get';
}
// 建立攔截器鏈. dispatchRequest 是重中之重,後續重點
var chain = [dispatchRequest, undefined];
// 初始化一個promise對象,狀態爲resolved,接收到的參數爲已經處理合並過的config對象
var promise = Promise.resolve(config);
// push各個攔截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能爲undefined
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 請求攔截器逆序 注意此處的 forEach 是自定義的攔截器的forEach方法
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 響應攔截器順序 注意此處的 forEach 是自定義的攔截器的forEach方法
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 循環攔截器的鏈
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift()); // 每一次向外彈出攔截器
}
return promise;
};
複製代碼
在這個方法中 用到了 InterceptorManager.js
'use strict';
var utils = require('./../utils');
// 攔截器的初始化 其實就是一組鉤子函數
function InterceptorManager() {
this.handlers = [];
}
// 調用攔截器實例的use時就是往鉤子函數中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
// 這個我也不知道幹啥的
return this.handlers.length - 1;
};
// 攔截器是能夠取消的,根據use的時候返回的ID,把某一個攔截器方法置爲null
// 不能用 splice 或者 slice 的緣由是 刪除以後 id 就會變化,致使以後的順序或者是操做不可控
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 這就是在 Axios的request方法中 中循環攔截器的方法 forEach 循環執行鉤子函數
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
複製代碼
在咱們項目當中 執行下邊代碼 說明添加了一個 請求攔截器
axios.interceptors.request.use((config) => {
// 思考 爲何 用 return
return config
}, error => {
return error
})
複製代碼
這個時候 會調用 InterceptorManager.js 的 use 方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
複製代碼
咱們把添加攔截器的回調函數 保存到了 handlers 中
當咱們執行 axios({ url: 'xxx' }) 調用了 Axios.prototype.request 這個方法 在這個方法當中 又調用了 InterceptorManager.js 的 forEach 方法
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
// 當前的 鉤子函數 { fulfilled: fulfilled, rejected: rejected }
fn(h);
}
});
};
複製代碼
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
複製代碼
// 其實 到這裏咱們知道了 攔截器的 添加過程 ,只不過 chain 具體幹了什麼還不清楚
// 咱們先看一下 下邊的一個例子 // 執行 this.interceptors.request.forEach 是否是獲得了 下邊的一個數組
// 其實 chain 最後的結果是
let chain = [
// 還記得 讓思考的一個問題, 爲何 在 axios.interceptors.request.use 添加攔截器的回調函數中 執行 return config 嗎?
function fulfilled (config) {
// 這個方法 就是咱們在執行 axios.interceptors.request.use 得第一個參數
// 這裏邊的代碼就是 use 第一個參數的代碼
config.name = 'zhang'
return config
},
function rejected () {
// 這個是 第二個參數
},
function fulfilled (config) {
// 若是不執行 都 不執行 return 你們本身寫一個demo 把這段代碼 執行如下
config.age = 20
return config
},
function rejected () {},
......
]
複製代碼
// 而後咱們定義個 Promis, resolve 接收的是一個 對象, 是否是至關於 咱們傳入的 請求 的 data 數據
var promise = Promise.resolve({
name: 'wang',
age: 27
});
while (chain.length) {
// 思考 爲何 能夠鏈式操做, 其實就是 攔截器回調函數 return config的做用
promise = promise.then(chain.shift(), chain.shift());
}
// Axios.prototype.request return 的 就是 promise
// 這裏咱們直接調用 來進行模擬
promise.then( (config) => {
console.log(config) // {name: "zhang", age: 20}
})
複製代碼