簡樸完成 JavaScript 模塊加載

JavaScript言語官方未完成定名空間,我們定義一個define函數以完成定名空間。define函數的運用如:

define(function(exports, module, require) {
    const $ = require('http://path/to/defined-jquery');
    $(function(){
        // dom ready!!!
    });
});

我們能夠如許完成:

(function(global) {
    'use strict';
    var errMsg = Math.random().toString(32).substr(2);
    // rootModule 對象保存着一切已加載的模塊
    var rootModule = {};

    // 每個模塊實例都有id屬性作為require時查找的標識符,
    // exports屬性作為對外暴露的對象,loaded屬性示意是不是加載完。
    function ModuleCtor(id) {
        if (!this || this.__proto__ !== ModuleCtor.prototype) {
            return new ModuleCtor(id);
        }
        this.id = id;
        this.exports = {};
        this.loaded = !1;
    }

    function define(id, fn) {
        // 手動賦值模塊id,兼容一個script里有多個define。
        if (typeof id === 'function') {
            fn = id;
            id = document.currentScript
                ? document.currentScript.src
                : Math.random()
                      .toString(32)
                      .substr(2);
        }
        if (typeof id !== 'string') {
            id = '' + id;
        }
        var module = ModuleCtor(id);
        exec();
        function __require__(src) {
            // 假如依靠已加載過直接返回module.exports,
            // 假如沒有加載過則經由過程jsonp加載,而且拋出一個非常來打斷原函數實行,在子模塊加載終了后從新實行原函數模仿異步代碼壅塞同步實行。
            if (rootModule[src] && rootModule[src].__proto__ === ModuleCtor.prototype) {
                return rootModule[src].exports;
            }
            loadScript(src, function() {
                exec();
            });
            throw new Error(errMsg);
        }
        function exec() {
            // 將__require__函數傳入fn,來支撐模塊內援用其他模塊。
            try {
                fn.call(module.exports, module.exports, module, __require__);
                module.loaded = !0;
                rootModule[id] = module;
            } catch (err) {
                if (err.message !== errMsg) {
                    throw err;
                }
            }
        }
    }

    function loadScript(src, callback) {
        var script = document.createElement('script');
        script.src = src;
        script.onload = function() {
            callback && callback(src);
        };
        document.body.appendChild(script);
        return script;
    }
    // 暴露define給全局
    global.define = define;
})(window);

這個模塊加載的完成有許多不足,假如模塊內有許多require時會被實行許屢次,所以最好子模塊內都是函數不要有本身的狀況。seajs的依靠解決要領是,挪用函數的toString要領來取得函数字面量,然後在parse出模塊依靠,先加載依靠,每個依靠加載完成后都emit一個事宜,當一切依靠都加載終了后,才實行factory函數,factory函數只實行一次,然則模塊加載的遞次和require的遞次基礎沒有關係(併發要求,誰都有能夠先到)。

======= 道貌岸然的分割線 ======
趁便吐槽一下seajs,由於某種原因,我再8102年見到了seajs而我在3000年前沒用過。文檔一直沒有交卸require(‘caonima’);是怎樣打斷函數實行的。看了源碼發現是用了Function.prototype.toString要領,然後剖析依靠併發加載(require函數是沒有遞次之分的)。看源碼前,我本身為了模仿該行動,經由過程拋出非常再重複的從新實行也完成了一個文件加載,而且我這個更賤的貨照樣真的同步引入依靠,越發cmd一些。

附 Webpack 模塊加載道理:

(function(modulesArr) {
    var rootModule = {};
    function __require__(id) {
        if (rootModule[id]) {
            return rootModule[id].exports;
        }
        var currentModule = modulesArr[id];
        var module = {
            id,
            exports: {}
        };
        currentModule.call(module.exports, module.exports, module, __require__);
        currentModule[id] = module;
        return module.exports;
    }
    return __require__(0);
})([
    function(exports, module, require) {
        var m1 = require(1);
        console.log(m1);
    },
    function(exports, module, require) {
        exports.msg = 'Hello World';
        var m2 = require(2);
        m2();
    },
    function(exports, module, require) {
        module.exports = function() {
            var str = 'Hello World';
            console.log(str);
            return str;
        };
    }
]);
    原文作者:codeboy
    原文地址: https://segmentfault.com/a/1190000015354138
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞