本文需對js
、es6
、webpack
、網絡請求
等基礎知識有基本瞭解php
相信axios
你們都用過或者什麼其餘的網絡請求
相關的庫,什麼ajax
、fly.js
等等等等,光我用過的請求庫就七八個,都大同小異html
本文並非要完徹底全一個字不差的實現axios
全部功能,沒有意義,可是也會實現的七七八八,主要是感覺一下這個流程和架子、以及 這個項目怎麼才能易於拓展、是否是易於測試的、可讀性怎麼樣等等等等webpack
廢話很少說,開搞~ios
老規矩先建一個空目錄,而後打開命令行執行c++
yarn init -y
git
或es6
cnpm init -y
github
而後是引入webpack
,雖然本節不主講webpack
,這裏我稍微提一嘴,webpack
和webpack-cli
不光項目裏要下載,全局也要下載,也就是 yarn global add webpack webpack-cli
web
執行命令,主要就須要這幾個包,幫忙編譯和調試的,babel
幫助儘可能兼容瀏覽器的,畢竟我們寫代碼確定充滿了es6
ajax
yarn add webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env
接下來再在根目錄建立webpack.config.js
來配置一下webpack
,而後再建一個src
目錄,來存放我們這個庫的代碼,如今的目錄就會是這個樣子
先簡單配置一下,後續有需求在加,這裏就直接上代碼了
~ webpack.config.js
const path = require('path'); module.exports = function() { const dev = true; return { mode: dev ? 'development' : 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: dev ? 'axios.js' : 'axios.min.js', sourceMapFilename: dev ? 'axios.map' : 'axios.min.map', libraryTarget: 'umd', }, devtool: 'source-map', }; }; 複製代碼
這時候在src
裏面,建一個index.js
,而後隨便寫點東西,像這樣
而後終端執行webpack
命令
固然了,如今確定是不兼容的,要不我們一開始也不用下babel
了,我們能夠試試,好比我如今index.js
加一句話
而後編譯完能夠看到結果也仍是let
,這確定不行
好的,那麼接下來就是配置babel
,沒什麼可說的,這裏直接放代碼了,沒什麼可說的
const path = require('path'); module.exports = function() { const dev = true; return { mode: dev ? 'development' : 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: dev ? 'axios.js' : 'axios.min.js', sourceMapFilename: dev ? 'axios.map' : 'axios.min.map', libraryTarget: 'umd', }, devtool: 'source-map', module: { rules: [ { test: /\.js$/i, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, }, ], }, }; }; 複製代碼
而後,你們確定不但願每次修改手動的去webpack
一下對吧?把webpack-dev-server
引進來
~ webpack.config.js
const path = require('path'); module.exports = function() { const dev = true; return { mode: dev ? 'development' : 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: dev ? 'axios.js' : 'axios.min.js', sourceMapFilename: dev ? 'axios.map' : 'axios.min.map', libraryTarget: 'umd', }, devtool: 'source-map', module: { rules: [ { test: /\.js$/i, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, }, ], }, devServer: { port: 8000, open: true, }, }; }; 複製代碼
這時候,直接終端裏運行webpack-dev-server
的話其實他會自動去找全局的模塊,這樣很差,因此。。。你懂的
直接package.json
里加上命令
~ package.json
{ "name": "axios", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start": "webpack-dev-server" }, "dependencies": { "@babel/core": "^7.7.7", "@babel/preset-env": "^7.7.7", "babel-loader": "^8.0.6", "webpack": "^4.41.5", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1" } } 複製代碼
而後yarn start
這時候會彈出一個html
固然了,默認是去找根下的index.html
,我們沒有,因此在根下建一個,而後引入我們的axios.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>axios</title> </head> <body> <script src="/axios.js"></script> </body> </html> 複製代碼
刷新頁面就會看到src/index.js
裏的alert
生效了
而且 webpack-dev-server
也是能夠的,改了代碼頁面會自動刷新
而後,我們就來配一下build
這裏就很少廢話了,直接上代碼了
~ package.json
{ "name": "axios", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start": "webpack-dev-server --env.dev", "build": "webpack --env.prod" }, "dependencies": { "@babel/core": "^7.7.7", "@babel/preset-env": "^7.7.7", "babel-loader": "^8.0.6", "webpack": "^4.41.5", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1" } } 複製代碼
~ webpack.config.json
const path = require('path'); module.exports = function(env={}) { const dev = env.dev; return { mode: dev ? 'development' : 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: dev ? 'axios.js' : 'axios.min.js', sourceMapFilename: dev ? 'axios.map' : 'axios.min.map', libraryTarget: 'umd', }, devtool: 'source-map', module: { rules: [ { test: /\.js$/i, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, }, ], }, devServer: { port: 8000, open: true, }, }; }; 複製代碼
能夠看到也是沒問題的~
好的到這我本算是把webpack
相關的東西搭的差很少了,接下來就要開始忙正事了~
首先我們先來建一個common.js
,用來放公共的方法,先寫一個斷言
~ /src/common.js
export function assert(exp, msg = 'assert faild') { if (!exp) { throw new Error(msg); } } 複製代碼
而後再建一個文件(我的習慣)/src/axios
主要的文件放在這
而後你們來看看axios
正經怎麼用,是否是能夠直接 axios({...})
或者axios.get
等等
在index.js
引入直接寫一下結果,把指望用法寫上,而後來補充內部怎麼寫
~ index.js
import axios from './axios'; console.log(axios); axios({ url: '1.txt', method: 'post' }); axios.get('1.txt', { headers: { aaa: 123 } }); axios.post( '1.txt', { data: 123 }, { headers: { bbb: 123, }, }, ); 複製代碼
這時候就要考慮考慮了,我們能夠直接寫函數,這個沒問題,不過那樣太散了,我的不喜歡,不過也是能夠的,因此這裏我就寫成類了,因爲改爲類了那麼輸出出去的確定得是一個實例
,既然是實例的話,那麼確定也不能直接像函數同樣直接()運行
沒錯,這時候就能夠用到我們的proxy
了,js
的class
裏的constructor
裏是能夠return
東西的,若是對這東西不太熟,建議先去看看js
的class
,這裏就很少贅述,主要說明思想
簡單來講,我們能夠return
一個proxy
對象,來代理我們返回的結果,從而達到我們既能夠直接用class
的方式寫,用的時候也能夠直接跟函數同樣()
調用
而後先來打印一下看看
~
這時候看頁面的console
,這時候能夠看到axios
就是一個proxy
對象,像這樣
這時候還能看到一個報錯,由於我們如今返回的是proxy
對象,不是實例類了,沒有get
也是理所應當
可能有人會奇怪,爲何這個proxy
監聽的對象非要單獨監聽一個proxy
函數呢,直接監聽this
不就好了麼,注意,這實際上是不行的,瞭解proxy
的朋友應該知道,proxy
你用什麼監聽建立的這事兒很重要,若是你監聽的是一個對象,那仍是不能直接調用,若是要是想直接像函數同樣直接調用的話,那你監聽的也必須是一個函數
像這樣
而後我們來解決一下get
函數找不到的問題,來給proxy
加一個方法, 很簡單,能夠給proxy
加一個get
方法,有人來找他,直接返回從我這個類上找,不就完了麼,不過稍微要注意一下這個this
,直接寫this
的話指向的是這個proxy
function request() {} class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } get() { console.log('get'); } post() { console.log('post'); } delete() {} } let axios = new Axios(); export default axios; 複製代碼
這時候再看,就沒有報錯了,而且get
和post
也能console
出來
這時候我們就能夠接着寫數據請求了。。。。。嗎? 遠遠不夠
axios
的參數有不少的多樣性,我們想來大概總結一下
axios('1.txt',{}) axios({ url: '1.txt', method }) axios.get('1.txt') axios.get('1.txt',{}) axios.post.... 複製代碼
等等吧,這一點,怎麼才能把這麼複雜多源的參數統一處理
第二就是axios
能夠很是深度的定製參數,能夠全局也能夠單獨定製,攔截器,transfrom
什麼的,等等吧,默認值等等
首先先來一個default
,來定義一下這些默認值,這裏稍微說一下這個X-Request-By
算是一個不成文的規範,這也是廣泛請求庫都願意作的事,方便後臺去判斷你這個請求是來自ajax
仍是from
仍是瀏覽器的url
function request() {} const _default = { method: 'get', headers: { common: { 'X-Request-By': 'XMLHttpRequest', }, get: {}, post: {}, delete: {}, }, }; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } get() { console.log('get'); } post() { console.log('post'); } delete() {} } let axios = new Axios(); export default axios; 複製代碼
固然了,這裏圖簡單,簡單寫着幾個參數,本身喜歡能夠再加不少東西,好比data
的默認值等等,先夠用,後續不夠用再加
這時候,我們來思考一下,這個default
要加給誰,直接axios.default = _default
麼,固然不是,由於我們這個axios
到最後確定是須要axios.create
多個實例的,那麼這時候就不行了,互相影響,prototype
就更不用說了
其實也很簡單,每次建立的時候從_default
複製一份新的就能夠了,直接JSON.parse(JSON.stringify(_default))
包一下就能夠了,這也是性能最高的方式,而後稍微來改造一下代碼
function request() {} const _default = { method: 'get', headers: { common: { 'X-Request-By': 'XMLHttpRequest', }, get: {}, post: {}, delete: {}, }, }; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } get() { console.log('get'); } post() { console.log('post'); } delete() {} } Axios.create = Axios.prototype.create = function() { let axios = new Axios(); axios.default = JSON.parse(JSON.stringify(_default)); return axios; }; export default Axios.create(); 複製代碼
這裏是給原型和實例都加了一個create
方法,由於我們可能直接用axios.create()
也可能直接用axios()
,純加靜態方法或者實例方法都知足不了我們的需求
這時候咱們來實驗一下,先來console
一下axios.default
你會發現,undefined
,這是爲何呢,在這裏明明都已經添加了呀
由於這時候這個axios
並非一個對象,而是一個proxy
,我們還沒給proxy
加set
方法對不對,加什麼都加不上,這時候來改造一下代碼
function request() {} const _default = { method: 'get', baseUrl: "", headers: { common: { 'X-Request-By': 'XMLHttpRequest', }, get: {}, post: {}, delete: {}, }, }; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } get() { console.log('get'); } post() { console.log('post'); } delete() {} } Axios.create = Axios.prototype.create = function() { let axios = new Axios(); axios.default = JSON.parse(JSON.stringify(_default)); return axios; }; export default Axios.create(); 複製代碼
這時候再看瀏覽器,就會發現這個default
就有了
以及我們來create
兩個axios
,改一下參數試一下
兩個實例參數也不影響,這也是很好的第n
步,這時候我們就已經完成axios
的四分之一了
我們如今實例間是不影響了,不過我們改改參數的時候毫不止直接axios.default.xxx
這麼改,我們還應該有參數,像這樣
這裏我們能夠直接改造一下axios.create
方法
~ axios.js
... Axios.create = Axios.prototype.create = function(options={}) { let axios = new Axios(); axios.default = { ...JSON.parse(JSON.stringify(_default)), ...options }; return axios; }; ... 複製代碼
直接展開替換一下就好了對不對,可是,真的麼?
假設我們直接傳了一個對象,裏面有個headers
的話是否是就直接給我們的默認參數header
整個就得替換了呀,那這個就不太好,固然了,這個也看我們對本身這個庫的需求,若是我們就想這麼作,也到是沒啥毛病,問題以下
那麼這時候,我們就能夠用一個小小的遞歸來搞定了
~ axios.js
... Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; function merge(dest, src) { for (let name in src) { if (typeof src[name] == 'object') { if (!dest[name]) { dest[name] = {}; } merge(dest[name], src[name]); } else { dest[name] = src[name]; } } } merge(res, options); axios.default = res; return axios; }; ... 複製代碼
這時候再看,就沒問題了
接下來,我們先不急着去寫請求的參數,我們先把這個代碼稍微規劃規劃,整理整理,畢竟全放在一個文件裏,這個之後就無法維護了
目前拆分能夠分幾點
default
是否是能夠用一個單獨的文件來裝merge
函數確定是公用的,能夠放在我們的common.js
裏request
也應該單獨放在一個js
裏來定義廢話很少說,直接上代碼
~ request.js
export default function request() { } 複製代碼
~ default.js
export default { method: 'get', baseUrl: '', headers: { common: { 'X-Request-By': 'XMLHttpRequest', }, get: {}, post: {}, delete: {}, }, }; 複製代碼
~ common.js
export function assert(exp, msg = 'assert faild') { if (!exp) { throw new Error(msg); } } export function merge(dest, src) { for (let name in src) { if (typeof src[name] == 'object') { if (!dest[name]) { dest[name] = {}; } merge(dest[name], src[name]); } else { dest[name] = src[name]; } } } 複製代碼
~ axios.js
import _default from './default'; import { merge } from './common'; import request from './request'; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } get() { console.log('get'); } post() { console.log('post'); } delete() {} } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
這時候就感受乾淨很多了,對不對
在寫以前,我們先來看看axios
都有哪些支持的寫法,而後再去考慮怎麼寫
大概來看 除了那個總的axios({...})
這三種方法是否是差很少呀,固然了,axios
裏東西太多了,這裏就簡單實現這三個,說明問題爲主,你們有興趣能夠本身加,無非是體力活
固然,能夠看到axios
參數狀況仍是蠻多的,這時候我們應該直接統一處理,無論傳過來什麼參數,我們都返回一個axios({})
這種,最終統一處理,這不是方便麼
這裏直接來先判斷一下前兩種狀況
你會發現前兩種狀況除了這個method
都是同樣的,那這個我們就能夠抽出方法統一處理
~ axios.js
import _default from './default'; import { merge } from './common'; import request from './request'; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } _preprocessArgs(method, ...args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; } else { return undefined; } return options; } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
而後這時候,我們以封裝一個庫的視角,確定須要對參數進行各類各樣的校驗,類型等等,不對的話給他一個正經的報錯,幫助使用者來調試
我們以前在common.js
裏寫的assert
這時候就用上了
... get(...args) { let options = this._preprocessArgs('get', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); // ... } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); } } } ... 複製代碼
這裏的規則對應着上面寫的axios
使用方式,大致來講也差很少,把這些參數都校驗好了,接下來,我們就能夠寫具體的個性化的處理了
順便一說,這個地方固然也是能夠重用的,不過不必,搞了一通其實也沒減小多少代碼,而且賊亂,也看我的,你們不喜歡能夠本身修改
而後我們再來處理一下這個options
,而且console
一下
~ axios.js
import _default from './default'; import { merge, assert } from './common'; import request from './request'; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { console.log(fn, thisArg, args); }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; } else { return undefined; } console.log(options); return options; } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; console.log(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; console.log(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; console.log(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; console.log(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
這時候我們來測試一下
~ index.js
import Axios from './axios'; Axios.get('1.json'); Axios.get('1.json', { headers: { a: 12 } }); Axios.post('1.php'); Axios.post('1.php', { a: 12, b: 5 }); Axios.post('1.php', [12, 5, 6]); let form = new FormData(); Axios.post('1.txt', form); Axios.post('1.txt', 'dw1ewdq'); Axios.post('1.json', form, { headers: { a: 213, b: 132 } }); Axios.delete('1.json'); Axios.delete('1.json', { parmas: { id: 1 } }); 複製代碼
這時候能夠看到,妥妥的對不對?
而後呢。。。還沒忘,我們還須要處理直接apply
的狀況,也就是直接Axios()
這麼調用的時候
不廢話,直接上代碼,跟get
其實差很少
~ axios.js
import _default from './default'; import { merge, assert } from './common'; import request from './request'; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { let options = _this._preprocessArgs(undefined, args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], }; console.log(options); } else { assert(false, 'invaild args'); } } }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; } else { return undefined; } console.log(options); return options; } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; console.log(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; console.log(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; console.log(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; console.log(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
而後來測試一下
沒問題,固然爲何method
是undefined
,由於這時候還沒走到我們的default
呢,我們是在這決定默認請求值的,因此這裏直接給個undefined
就行,而後我們應該把些options
全都和defaulft
處理完,丟給我們的request
函數去請求
而這個方法確定全部請求都須要,因此我們寫一個公共方法
這個request
方法主要乾的事四件事
import _default from './default'; import { merge, assert } from './common'; import request from './request'; class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { let options = _this._preprocessArgs(undefined, args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], }; _this.request(options); } else { assert(false, 'invaild args'); } } }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; this.request(options); } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; this.request(options); } else { return undefined; } return options; } request(options) { console.log(options, 'request'); // 1. 跟this.default進行合併 // 2. 檢測參數是否正確 // 3. baseUrl 合併請求 // 4. 正式調用request(options) } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; this.request(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; this.request(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; this.request(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; this.request(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
這個合併很簡單,我們以前寫的merge
函數又派上用場了,修改一下代碼
... request(options) { console.log(options); // 1. 跟this.default進行合併 merge(options, this.default); console.log(options); // 2. 檢測參數是否正確 // 3. baseUrl 合併請求 // 4. 正式調用request(options) } ... 複製代碼
這時候能夠看到,合併前和合並後數據就已經都有了,可是,這時候我們的header
就不該該是所有都有了,應該根據傳過來的method
是什麼來把對應的方式的header
和common
合併一下
request(options) { // 1. 跟this.default進行合併 let _headers = this.default.headers; delete this.default.headers; merge(options, this.default); this.default.headers = _headers; //合併頭 // this.default.headers.common -> this.default.headers.get -> options let headers = {}; merge(headers, this.default.headers.common); merge(headers, this.default.headers[options.method.toLowerCase()]); merge(headers, options.headers); console.log(headers); console.log(options); // 2. 檢測參數是否正確 // 3. baseUrl 合併請求 // 4. 正式調用request(options) } 複製代碼
這裏比較亂,因此我們先來捋一捋
我們目的是要讓header
合併,不過合併的話就會有點小問題,以前在們在default
的裏定義的common
、get
...也都會被複制過來,若是要是我們用if
判斷 options.header.common == this.default.headers.common
而後delete
的話,這時候你會發現不行,由於我們也知道,你直接寫兩個對象判斷就至關於直接new
了兩個對象,那這時候判斷確定不相等,那麼我們是在何時複製的呢
就在我們封裝的merge
裏,以及還有不少地方都動過這個東西
而後,我們應該找到這個東西到底在何時就不同了,其實也就是我們這個request
函數裏第一次merge
的時候
因此我們這裏玩了一個小技巧,由於common
這些東西在底下都已經手動搞了,因此這裏不須要他複製進來,因此先delete
了一下
讓他在以前就進不去,delete
以後再給拿回去,兩頭不耽誤,真好~
最後,我們把headers
賦值到我們的options.headers
request(options) { // 1. 合併頭 let _headers = this.default.headers; delete this.default.headers; merge(options, this.default); this.default.headers = _headers; // this.default.headers.common -> this.default.headers.get -> options let headers = {}; merge(headers, this.default.headers.common); merge(headers, this.default.headers[options.method.toLowerCase()]); merge(headers, options.headers); options.headers = headers; console.log(options); // 3. baseUrl 合併請求 // 4. 正式調用request(options) } 複製代碼
~ index.js
import Axios from './axios'; Axios('1.php'); Axios({ url: '2.php', params: { a: 12, b: 3 }, headers: { a: 12, }, }); 複製代碼
測試一下結果
能夠看到,沒毛病~
而後我們再來看一下第二步,其實這個校驗我們能夠寫的很是很是多值得校驗的事兒,可是這裏只說明意思,寫幾個就算,你們有興趣能夠多補一些
... assert(options.method, 'no method'); assert(typeof options.method == 'string', 'method must be string'); assert(options.url, 'no url'); assert(typeof options.url == 'string', 'url must be string'); ... 複製代碼
第三步也是直接上代碼了
~ axios.js
options.url=options.baseUrl+options.url; delete options.baseUrl; 複製代碼
~ common.js
export function assert(exp, msg = 'assert faild') { if (!exp) { throw new Error(msg); } } export function merge(dest, src) { for (let name in src) { if (typeof src[name] == 'object') { if (!dest[name]) { dest[name] = {}; } merge(dest[name], src[name]); } else { if (dest[name] === undefined) { dest[name] = src[name]; } } } } 複製代碼
~ index.js
import Axios from './axios'; Axios('1.php', { baseUrl: 'http://www.baidu.com/', headers: { a: 12, }, }); Ï 複製代碼
這時候再測試一下,能夠看到,就沒問題了
這裏說明一下爲何要改一下
merge
,加了一個判斷,由於我們以前是直接替換掉,有沒有都替換,這確定不行,不加上的話會把我們的baseUrl
幹了
固然了,還有個小事兒我們須要處理一下,若是用你這個庫的人腦子有病(固然,不考慮腦子有病的也能夠),他寫路徑的時候是這麼寫的
你這個是否是又不行了呀,很簡單,NodeJS
裏有一個url
包,能夠直接引過來用,webpack
會幫你把他打包起來,不過有一點須要注意,webpack
不是全部的東西都能打包的,好比fs
模塊,甚至一些底層功能用c
和c++
寫的系統包這確定就不行,不過一個url
問題不大
~ axios.js
import _default from './default'; import { merge, assert } from './common'; import request from './request'; const urlLib = require('url'); class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { let options = _this._preprocessArgs(undefined, args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], }; _this.request(options); } else { assert(false, 'invaild args'); } } }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; this.request(options); } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; this.request(options); } else { return undefined; } return options; } request(options) { // 1. 合併頭 let _headers = this.default.headers; delete this.default.headers; merge(options, this.default); this.default.headers = _headers; // this.default.headers.common -> this.default.headers.get -> options let headers = {}; merge(headers, this.default.headers.common); merge(headers, this.default.headers[options.method.toLowerCase()]); merge(headers, options.headers); options.headers = headers; console.log(options); // 2. 檢測參數是否正確 assert(options.method, 'no method'); assert(typeof options.method == 'string', 'method must be string'); assert(options.url, 'no url'); assert(typeof options.url == 'string', 'url must be string'); // 3. baseUrl 合併請求 options.url = urlLib.resolve(options.baseUrl, options.url); delete options.baseUrl; // 4. 正式調用request(options) request(options); } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; this.request(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; this.request(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; this.request(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; this.request(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
這時候再測試一下
就沒問題了
其實我們這個merge
如今還有很大的問題,由於我們最開始的想要的功能和我們如今的功能有差入
我們如今是被迫寫了一個if
,改爲了查漏補缺,那麼其實後面有優先級的東西順序應該都反過來
我們最開始的需求是什麼,是想讓這個dest
優先級最低,是能夠被別人覆蓋的,可是如今寫了這個if
以後,變成他優先級最高了,那這個就不對,可是還不能去掉,去掉了以後這個合併就又出問題了
this.default
變了,這時候又須要複製一份,我們來寫一個公共的方法放到
common.js
裏
~ common.js
... export function clone(obj) { return JSON.parse(JSON.stringify(obj)); } 複製代碼
以及我們這個順序稍微顛倒一下,而後數據克隆一下
這個時候來測試一下
會發現是否是header
裏的這些東西又回來了呀,緣由也很簡單,由於我們在上面整個的default
給clone
下來了,因此我們把delete
這一塊往上提提
這時候就沒問題了
這個時候我們就應該來寫第四步了 直接把options
給到我們的request
函數裏就能夠,零零碎碎的問題全都給它處理好了
而後來修改一下request.js
~ request.js
export default function request(options) { console.log(options); let xhr = new XMLHttpRequest(); xhr.open(options.method, options.url, true); for (let name in options.headers) { xhr.setRequestHeader(name, options.headers[name]); } xhr.send(options.data); } 複製代碼
暫時先寫一個簡單的請求,而後我們來測試一下看看能不能發出去
首先先建一個txt
,方便我們測試,我就放到data
目錄裏了,像這樣
而後改一下index.js
~ index.js
import Axios from './axios'; Axios('/data/1.txt', { headers: { a: 12, }, }); 複製代碼
這時候我們能夠看到,header
是加上了的,而且返回的東西也對
固然這個東西還沒完,也是一個防止用戶搗亂的事
若是用戶給你的header
是這樣的東西,那這個就不太好,因此最好仍是給它來一個編碼
~ request.js
export default function request(options) { console.log(options); let xhr = new XMLHttpRequest(); xhr.open(options.method, options.url, true); for (let name in options.headers) { xhr.setRequestHeader( encodeURIComponent(name), encodeURIComponent(options.headers[name]), ); } xhr.send(options.data); } 複製代碼
這就沒問題了萬一後臺有點問題,一遇見冒號就算結束這種問題就能避免了
而後我們用的時候確定更多的時候是須要一個async
和await
一下,因此我們須要用Promise
來包一下
~ axios.js
export default function request(options) { console.log(options); let xhr = new XMLHttpRequest(); xhr.open(options.method, options.url, true); for (let name in options.headers) { xhr.setRequestHeader( encodeURIComponent(name), encodeURIComponent(options.headers[name]), ); } xhr.send(options.data); return new Promise((resolve, reject) => { xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr); } else { reject(xhr); } } }; }); } 複製代碼
以及這個時候,我們還有不少不少問題
code
,這個怎麼作到配置webpack
只能兼容es6
,async
和await
是兼容不了的,這個該怎麼配我們先來解決webpack
的問題,這個其實很簡單,我們須要再按一個包 yarn add @babel/polyfill
而後打開webpack.config.js
修改一下entry
~ webpack.config.js
... entry: ['@babel/polyfill','./src/index.js'], ... 複製代碼
注意這個順序是不能顛倒的
這樣就能夠兼容了,而後我們再來修改一下index.js
import Axios from './axios'; (async () => { let res = await Axios('/data/1.txt', { headers: { a: 12, b: '321fho:fdsf vfds; : ', }, }); console.log(res); })(); 複製代碼
能夠看到結果是undefined
,這是由於我們根本沒有返回我們的處理結果
這時候修改一下axios.js
import _default from './default'; import { merge, assert, clone } from './common'; import request from './request'; const urlLib = require('url'); class Axios { constructor() { let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { let options = _this._preprocessArgs(undefined, args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], }; return _this.request(options); } else { assert(false, 'invaild args'); } } }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; this.request(options); } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; this.request(options); } else { return undefined; } return options; } request(options) { // 1. 合併頭 let _headers = this.default.headers; delete this.default.headers; let result = clone(this.default); merge(result, this.default); merge(result, options); this.default.headers = _headers; options = result; // this.default.headers.common -> this.default.headers.get -> options let headers = {}; merge(headers, this.default.headers.common); merge(headers, this.default.headers[options.method.toLowerCase()]); merge(headers, options.headers); options.headers = headers; // 2. 檢測參數是否正確 assert(options.method, 'no method'); assert(typeof options.method == 'string', 'method must be string'); assert(options.url, 'no url'); assert(typeof options.url == 'string', 'url must be string'); // 3. baseUrl 合併請求 options.url = urlLib.resolve(options.baseUrl, options.url); delete options.baseUrl; // 4. 正式調用request(options) return request(options); } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; return this.request(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; return this.request(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; return this.request(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; return this.request(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = { ...JSON.parse(JSON.stringify(_default)) }; merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
這時候我們再看結果,是否是就能夠了,固然了,我們確定不能就直接把原始的xml
對象返回回去,我們還要對返回的數據進行各類處理
我們先來簡單的改一下axios.js
下的request
返回又一個promise
,這時候能夠看到結果,一點毛病沒有
可是我們這個東西全放在axios.js
裏就太亂了,因此我們也單獨把它拆除去
建兩個文件一個是/src/response.js
一個是/src/error.js
而後這裏axios.js
引入一下,而且處理的時候分別交給他們
而後response.js
裏直接返回值就能夠了
不過這個headers
稍微有點特別,須要單獨調一個方法xhr.getAllResponseHeaders()
,不過這返回的是原始xhr
的頭,這確定不行,因此我們須要來切一下
~ response.js
export default function(xhr) { let arr = xhr.getAllResponseHeaders().split('\r\n'); let headers = {}; arr.forEach(str => { if (!str) return; let [name, val] = str.split(': '); headers[name] = val; }); return { ok: true, status: xhr.status, statusText: xhr.statusText, data: xhr.response, headers, xhr, }; } 複製代碼
這樣就好了
transformRequest
&& transformResponse
這時候固然還不算完,由於我們如今的data
尚未任何處理,因此確定都是字符串,以及用戶可能自定義這個處理方式,熟悉axios
的朋友應該知道,axios
有transformRequest
和transformResponse
方法
這時候我們先來改一下以前axios.js
裏的request
方法
如今須要在第三步和第四步之間來處理一下請求
先把參數打印一下,而後修改一下index.js
的測試demo
爲了測試方便我把1.txt
改爲1.json
了,方便我們一會處理json
數據好看到效果
能夠看到,這個參數是能夠拿到的,那麼接下來就比較簡單了,直接來
這時候看請求,這個headers
就加上了
這裏稍微提一嘴,爲何我要delete
掉,雖然不delete
沒什麼關係,可是我但願我這個request
保持乾淨
至於這個自定義返回結果,是否是就更簡單了呀
這時候能夠看眼結果,我沒傳transformResponse
,結果是這樣
這時候就能夠了
固然了,我們如今已經能夠很靈活的運用了,不止單傳這個json
裏能夠配參數,全局配置統一處理同樣是能夠的,我們來試試
以及不一樣的實例之間,都是能夠的
攔截器在一個請求庫裏確定是必不可少的,其實我們這個庫寫到如今,想加一個這玩意,實際上是很容易的
~ index.js
import Axios from './axios'; Axios.interceptors.request.use(function(config) { config.headers.interceptors = 'true'; return config; }); (async () => { let res = await Axios('/data/1.json', { headers: { a: 12, b: '321fho:fdsf vfds; : ', }, }); console.log(res); })(); 複製代碼
而後新建一個interceptor.js
~interceptor.js
class Interceptor { constructor() { this._list = []; } use(fn) { this._list.push(fn); } list() { return this._list; } } export default Interceptor; 複製代碼
~ axios.js
import _default from './default'; import { merge, assert, clone } from './common'; import request from './request'; import createResponse from './response'; import createError from './error'; const urlLib = require('url'); import Interceptor from './interceptor'; class Axios { constructor() { this.interceptors = { request: new Interceptor(), response: new Interceptor(), }; let _this = this; return new Proxy(request, { get(data, name) { return _this[name]; }, set(data, name, val) { _this[name] = val; return true; }, apply(fn, thisArg, args) { let options = _this._preprocessArgs(undefined, args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], }; return _this.request(options); } else { assert(false, 'invaild args'); } } }, }); } _preprocessArgs(method, args) { let options; if (args.length == 1 && typeof args[0] == 'string') { options = { method, url: args[0] }; this.request(options); } else if (args.length == 1 && args[0].constructor == Object) { options = { ...args[0], method, }; this.request(options); } else { return undefined; } return options; } request(options) { // 1. 合併頭 let _headers = this.default.headers; delete this.default.headers; let result = clone(this.default); merge(result, this.default); merge(result, options); this.default.headers = _headers; options = result; // this.default.headers.common -> this.default.headers.get -> options let headers = {}; merge(headers, this.default.headers.common); merge(headers, this.default.headers[options.method.toLowerCase()]); merge(headers, options.headers); options.headers = headers; // 2. 檢測參數是否正確 assert(options.method, 'no method'); assert(typeof options.method == 'string', 'method must be string'); assert(options.url, 'no url'); assert(typeof options.url == 'string', 'url must be string'); // 3. baseUrl 合併請求 options.url = urlLib.resolve(options.baseUrl, options.url); delete options.baseUrl; // 4. 變換一下請求 const { transformRequest, transformResponse } = options; delete options.transformRequest; delete options.transformResponse; if (transformRequest) options = transformRequest(options); let list = this.interceptors.request.list(); list.forEach(fn => { options = fn(options); }); // 5. 正式調用request(options) return new Promise((resolve, reject) => { return request(options).then( xhr => { let res = createResponse(xhr); if (transformResponse) res = transformResponse(res); let list = this.interceptors.response.list(); list.forEach(fn => { res = fn(res); }); resolve(res); }, xhr => { let err = createError(xhr); reject(err); }, ); }); } get(...args) { let options = this._preprocessArgs('get', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; return this.request(options); } else { assert(false, 'invaild args'); } } } post(...args) { let options = this._preprocessArgs('post', args); if (!options) { if (args.length == 2) { assert(typeof args[0] == 'string', 'args[0] must is string'); options = { url: args[0], data: args[1], method: 'post', }; return this.request(options); } else if (args.length == 3) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[2] == 'object' && args[2] && args[2].constructor == Object, 'args[2] must is JSON', ); options = { ...args[2], url: args[0], data: args[1], method: 'post', }; return this.request(options); } else { assert(false, 'invaild argments'); } } } delete(...args) { let options = this._preprocessArgs('delete', args); if (!options) { assert(typeof args[0] == 'string', 'args[0] must is string'); assert( typeof args[1] == 'object' && args[1] && args[1].constructor == Object, 'args[1] must is JSON', ); options = { ...args[1], url: args[0], method: 'get', }; return this.request(options); } } } Axios.create = Axios.prototype.create = function(options = {}) { let axios = new Axios(); let res = clone(_default); merge(res, options); axios.default = res; return axios; }; export default Axios.create(); 複製代碼
能夠看到,基本上是同樣的套路,主要就是把參數傳過來而後調用一下而已
不過這裏面有兩個小小的問題須要處理
config
不對或者沒返回我們理應給他返回錯誤信息,再校驗一下,這時候你們應該能想到了,應該吧axios
校驗這些參數單獨搞一個函數,沒什麼技術含量,這裏就很少贅述,你們有興趣能夠試一下forEach
,這時候就會致使一個問題,若是用戶給你的是一個帶async
的函數那就不行了,我們也要加上async
和await
,不過async
和await
裏面返回一個promise
又很怪,這個你們有興趣能夠本身試試,或者評論區留言這時候來試一下效果
能夠看到我們這個攔截器也算是完成了
本篇最終代碼已上傳github
,連接以下 github.com/Mikey-9/axi…
仍是那句話,我們本篇文章主要不是要完整的實現axios
,而是實現一個這樣的庫的思想,固然其中也有不少問題,歡迎你們在評論區留言或者能夠加個人qq
或者微信
一塊兒交流
篇幅略長,感謝觀看
Thank You
qq:
微信: