axios 詳細講解 , 每一個步驟帶簡單 demo

爲何 使用axios

  1. 支持瀏覽器和node.js
  2. 支持promise
  3. 能攔截請求和響應
  4. 能轉換請求和響應數據
  5. 能取消請求
  6. 自動轉換JSON數據
  7. 瀏覽器端支持防止CSRF(跨站請求僞造)

axios 項目目錄結構

├── /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

模擬 axios.js 入口文件 createInstance 的方法

  1. 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);
        };
    };
    複製代碼
  2. 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);
                }
            }
        }
    }
    複製代碼
  3. 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;
    }
    複製代碼

axios 核心方法 request 之攔截器

首先咱們看一下這個方法都幹了什麼瀏覽器

  1. 設置 咱們傳入的 配置config
  2. 添加請求攔截器requestInterceptors、響應攔截器 responseInterceptors
  3. 發送請求 dispatchRequest

我先把 攔截器的代碼註釋給寫一下, 若是沒有明白看我下邊寫的小栗子 就能完全明白攔截器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}
    })
複製代碼

求點贊, 給點動力繼續寫

相關文章
相關標籤/搜索