異步模塊模式

簡介

  衆所周知,模塊化開是會將複雜的系統分解成高內聚、低耦合的模塊,使系統開發變得可控、可維護、可拓展,提升模塊複用率。而在js中,異步模塊模式的狀況則比較多,所謂異步模塊模式,是在請求發出後,繼續其餘業務邏輯,直到模塊加載完成後執行後續的邏輯,實現模塊開發中對模塊加載完成後的引用。git

  今天就來分析一下異步加載模塊,本文經過建立與調度模塊、加載模塊和設置模塊三個方面來分析github

建立與調度模塊

  建立與調度方法集模塊建立方法於一身。在這個方法中腰遍歷全部依賴模塊,並判斷全部模塊都存在纔可執行回調函數,不然加載相應文件,直到文件加載完成才執行回調函數。ajax

實現代碼以下:數組

/**
     * 建立或調用模塊方法
     * @param url           模塊url
     * @param modDeps       依賴模塊
     * @param modCallback   模塊主函數
     */
    F.module = function(url, modDeps, modCallback) {
        // 將參數轉化爲數組
        let args = [].slice.call(arguments);
        // 獲取模塊構造函數(參數數組中最後一個參數成員)
        let callback = args.pop();
        // 獲取依賴模塊(緊鄰回調函數參數,且數據類型爲數組)
        let deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [];
        // 該模塊url(模塊ID)
        url = args.length ? args.pop() : null;
        //  依賴模塊序列
        let params = [];
        // 未加載的依賴模塊數量統計
        let depsCount = 0;
        // 依賴模塊序列中索引值
        let i = 0;
        // 依賴模塊序列長度
        let len;

        if(len = deps.length) {
            while(i < len) {
                (function(i) {
                    // 增長未加載依賴模塊數量統計
                    depsCount++;
                    // 異步加載依賴模塊
                    loadModule(deps[i], function(mod) {
                        // 依賴模塊序列中添加依賴模塊數量統一減一
                        depsCount--;
                        params[i] = mod;
                        // 若是依賴模塊所有加載
                        if(depsCount === 0) {
                            // 在模塊緩存器中矯正該模塊,並執行構造函數
                            setModule(url, params, callback);
                        }
                    });
                })(i);
                // 遍歷下一個模塊
                i++;
            }
            // 無依賴模塊,直接執行回調函數
        } else {
            // 在模塊緩存器中矯正該模塊,並執行構造函數
            setModule(url, [], callback);
        }
    }

在module方法中有兩個方法尚未定義:loadModule(加載模塊)和setModule(設置模塊),接下來,便一一介紹緩存

加載模塊

loadModule方法目的是加載依賴模塊對應的文件並執行回調函數。對此可分三種狀況處理:app

  1. 若是文件已經被要求加載過,咱們要區分文件已經加載完成或是正在加載中,若是已經完成,咱們異步執行該模塊的加載完成回調函數(相見F.module方法中對loadModule方法調用部分)
  2. 若是文件未加載完成,咱們要將加載完成回調函數緩存入模塊加載完成回調函數容器中(該模塊的onload數組容器)。
  3. 若是依賴模塊對應的文件未被要求加載過,那麼咱們要加載該文件,並將該依賴模塊的初始化信息寫入模塊緩存器中

代碼以下:異步

 // 模塊緩存器。存儲已建立模塊
    let moduleCache = {};
 /**
     * 異步加載依賴模塊所在文件
     * @param moduleName        模塊路徑(id)
     * @param callback          模塊加載完成回調函數
     */
    function loadModule(moduleName, callback) {
        let _module;
        // 若是依賴模塊被要求加載過
        if(moduleCache[moduleName]) {
            _module = moduleCache[moduleName];
            // 若是模塊加載完成
            if(_module.status === 'loaded') {
                // 執行模塊加載完成後回調函數
                setTimeout(callback(_module.exports), 0);
            } else {
                // 緩存該模塊所處文件加載完成回調函數
                _module.onload.push(callback);
            }
            // 模塊第一次被依賴引用
        } else {
            // 緩存該模塊初始化信息
            moduleCache[moduleName] = {
                // 模塊ID
                moduleName: moduleName,
                // 模塊對應文件加載狀態(默認加載中)
                status: 'loading',
                // 模塊接口
                exports: null,
                // 模塊對應文件加載完成回調函數緩衝器
                onload: [callback]
            };
            // 加載模塊對應文件
            loadScript(getUrl(moduleName), moduleName);
        }
    }

loadModule方法中在加載模塊對應文件時須要引用loadScript加載腳本方法和getUrl獲取文件路徑方法。這兩個方法等實現以下:async

function getUrl(moduleName) {
        // 拼接完整的文件路徑字符串,如'lib/ajax' => 'lib/ajax.js'
        return String(moduleName).replace(/\.js$/g, '') + '.js';
    }

    function loadScript(src, id) {
        let _script = document.createElement('script');
        // 文件類型
        _script.type = 'text/JavaScript';
        // 確認編碼
        _script.charset = 'UTF-8';
        // 異步加載
        _script.async = true;
        // 文件路徑
        _script.src = src;
        // 文件id
        _script.id = id;
        document.getElementsByTagName('head')[0].appendChild(_script);
    }

  由getUrl方法能夠看出,模塊的id和文件的路徑應該是一一對應的關係,否則在解析路徑時就會出錯。不過,也能夠將getUrl改形成你喜歡的格式,在這裏就很少說了。模塊化

設置模塊

表面上看設置模塊就是執行回調函數的,但實質上它作了三件事:函數

  1. 對建立的模塊來講,當個人全部依賴模塊加載完成時,我要使用該方法;
  2. 對應被依賴的模塊來講,其所在的文件加載後要執行該依賴模塊(即建立該模塊過程)又間接地使用該方法;
  3. 對於一個匿名函數來講(F.module方法中無URL參數數據),執行過程當中也會使用該方法;

設置模塊代碼以下:

/**
     * 設置模塊並執行模塊構造函數
     * @param moduleName        模塊ID名稱
     * @param params            依賴模塊
     * @param callback          模塊構造函數
     */
    function setModule(moduleName, params, callback) {
        let fn;
        // 若是模塊被調用過
        if(moduleCache[moduleName]) {
            let _module = moduleCache[moduleName];
            // 設置模塊已經加載完成
            _module.status = 'loaded';
            // 矯正模塊接口
            _module.exports = callback ? callback.apply(_module, params) : null;
            // 執行模塊文件加載完成回調函數
            while(fn = _module.onload.shift()) {
                fn(_module.exports);
            }
        } else {
            // 模塊不存在(匿名模塊),則直接執行構造函數
            callback && callback.apply(null, params);
        }
        // 刪除加載的script標籤
        deleteScript(moduleName);
    }

在設置代碼執行後,便將加載到head中的script標籤刪除,純屬我的代碼潔癖,能夠沒有的,刪除標籤代碼以下:

function deleteScript(id) {
        const deleteJs = document.getElementById(id);
        console.log(deleteJs);
        if(deleteJs) {
            document.getElementsByTagName('head')[0].removeChild(deleteJs);
        }
    }

最後

  異步模塊模式不只減小了多人開發過程當中變量、方法名被覆蓋的問題,並且增長了模塊依賴,使開發者沒必要擔憂某些方法還沒有加載或未加載完成形成的沒法使用問題。異步加載部分功能也能夠將更多首屏沒必要要的功能剝離出去,減小首屏加載成本。

demo能夠在此看到:

https://github.com/weiruifeng/async

相關文章
相關標籤/搜索