目錄
AMD__和__CMD
使用方法
實作一個自己的載入器
一個例子
首頁 web前端 js教程 JavaScript 模組化程式設計之載入器原理詳解

JavaScript 模組化程式設計之載入器原理詳解

Mar 06, 2017 pm 03:11 PM

世面上有好多JavaScript的載入器,例如 sea.js, require.js, yui loader, labJs…., 載入器的使用範圍是一些比較大的項目, 個人感覺如果是小專案的話可以不用,  我用過seaJS和requireJS, 在專案中用過requireJS, requireJS是符合AMD,全名是(Asynchronous Module Definition)即異步模組載入機制, seaJS是符合CMD規範的載入器。

AMD__和__CMD

AMD規格是依賴前置, CMD規格是依賴後置, AMD規範的載入器會把所有的JS中的依賴前置執行。 CMD是懶加載, 如果JS需要這個模組就加載, 否則就不加載, 導致的問題是符合AMD規範的加載器(requireJS), 可能第一次加載的時間會比較久, 因為他把所有依賴的JS全部一次下載下來;

常識,jQuery是支持AMD規範,並不支持CMD規範,也就是說, 如果引入的是seaJS,想要使用jQuery,要用alias配置, 或者直接把 http ://www.php.cn/ 直接引入頁面中;

//这是jQuery源码的最后几行, jQuery到了1.7才支持模块化;
// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.
// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// http://www.php.cn/
if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    });
};
登入後複製

使用方法

#例如我們可以這樣定義一個模組:

//文件所在的路径地址为:http://www.php.cn/:63342/module/script/dir2/1.js
define(function() {
    return "!!!!";
});
登入後複製

也可以這樣定義一個模組:

//这个文件的路径为http://www.php.cn/:63342/module/main.js ,
而且有一个依赖, 加载器会自动去加载这个依赖, 当依赖加载完毕以后, 会把这个依赖(就是script/dir2/1.js)执行的返回值作为这个函数的参数传进去;
require(["script/dir2/1.js"], function(module1) {
    console.log(module1);
});
//实际上会打印出 "!!!!"
登入後複製

一般來說,一個模組只能寫一個define函數, define函數的傳參主要有兩種方式:

1:正常上可以是一個函數;

2:可以是一個數組類型依賴的列表, 和一個函數;

如果一個模組寫了多個define會導致模組失靈, 先定義的模組被後定義的模組給覆蓋了(當然了, 一般我們不那樣玩);

一個模組內可以寫多個require, 我們可以直接理解require為匿名的define模組, 一個define模組內可以有多個require, 而且require過的模組會被快取起來, 這個快取的變數通常是在閉包內, 而且名字多數叫modules什麼的…..;

我們透過載入器開發實現的模組化開發要遵守一個規範, 規範了一個模組為一個JS,那麼我們就可以新建幾個目錄為conroller,view, model, 也是為了後期更好的維護和解耦:

實作一個自己的載入器

使用的方式:

//这个模块依赖的四个模块,加载器会分别去加载这四个模块;
define(["依赖0","依赖1","依赖2","依赖3"], function(依赖0,依赖1,依赖2,依赖3){

});

//返回一个空对象
define(function(){
    return {};
});

//直接把require当作是define来用就好了;
require(["依赖0","依赖1","依赖2","依赖3"], function(依赖0,依赖1,依赖2,依赖3) {
    //执行依赖0;
    依赖0(依赖1,依赖2,依赖3);
});

//这个加载器define函数和require函数的区别是,define我们可以传个name作为第一参数, 这个参数就是模块的名字, 好吧, 不管这些了.....;
登入後複製

以下為載入器的結構,因為程式碼量已經很少了, 所以每一函數都是必須的, 為了不影響全局, 把程式碼放在匿名自執行函數內部:

(function() {
    定义一个局部的difine;
    var define;
    //我偷偷加了个全局变量,好调试啊;
    window.modules = {
    };
    //通过一个名字获取绝对路径比如传"xx.js"会变成"http://www.mm.com/"+ baseUrl + "xx.html";
    var getUrl = function(src) {};
    //动态加载js的模块;
    var loadScript = function(src) {};
    //获取根路径的方法, 一般来说我们可以通过config.baseUrl配置这个路径;
    var getBasePath = function() {};
    //获取当前正在加载的script标签DOM节点;
    var getCurrentNode = function() {};
    //获取当前script标签的绝对src地址;
    var getCurrentPath = function() {};
    //加载define或者require中的依赖, 封装了loadScript方法;
    var loadDpt = function(module) {};
    //这个是主要模块, 完成了加载依赖, 检测依赖等比较重要的逻辑
    var checkDps = function() {};
    定义了define这个方法
    define = function(deps, fn, name) {};
    window.define = define;
    //require是封装了define的方法, 就是多传了一个参数而已;
    window.require = function() {
        //如果是require的话那么模块的名字就是一个不重复的名字,避免和define重名;
        window.define.apply([], Array.prototype.slice.call(arguments).concat( "module|"+setTimeout(function() {},0) ));
    };
});
登入後複製

載入器原始碼實作(相容,chrome, FF, IE6 ==>> IE11), IE11沒有了readyState屬性, 也沒有currentScript屬性,坑爹啊,  無法取得目前正在執行的JS路徑, 所以要用hack;

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script>
    (function() {
        var define;
        window.modules = {
        };
        var getUrl = function(src) {
            var scriptSrc = "";
            //判断URL是否是
            // ./或者
            // /或者
            // 直接是以字符串开头
            // 或者是以http://开头;
            if( src.indexOf("/") === 0 || src.indexOf("./") === 0 ) {
                scriptSrc = require.config.base + src.replace(/^\//,"").replace(/^\.\//,"");
            }else if( src.indexOf("http:") === 0 ) {
                scriptSrc = src;
            }else if( src.match(/^[a-zA-Z1-9]/) ){
                scriptSrc = require.config.base + src;
            }else if(true) {
                alert("src错误!");
            };
            if (scriptSrc.lastIndexOf(".js") === -1) {
                scriptSrc += ".js";
            };
            return scriptSrc;
        };

        var loadScript = function(src) {
            var scriptSrc = getUrl(src);
            var sc = document.createElement("script");
            var head = document.getElementsByTagName("head")[0];
            sc.src = scriptSrc;
            sc.onload = function() {
                console.log("script tag is load, the url is : " + src);
            };
            head.appendChild( sc );
        };

        var getBasePath = function() {
            var src = getCurrentPath();
            var index = src.lastIndexOf("/");
            return  src.substring(0,index+1);
        };

        var getCurrentNode = function() {
            if(document.currentScript) return document.currentScript;
            var arrScript = document.getElementsByTagName("script");
            var len = arrScript.length;
            for(var i= 0; i<len; i++) {
                if(arrScript[i].readyState === "interactive") {
                    return arrScript[i];
                };
            };

            //IE11的特殊处理;
            var path = getCurrentPath();
            for(var i= 0; i<len; i++) {
                if(path.indexOf(arrScript[i].src)!==-1) {
                    return arrScript[i];
                };
            };
            throw new Error("getCurrentNode error");
        };

        var getCurrentPath = function() {
            var repStr = function(str) {
                return (str || "").replace(/[\&\?]{1}[\w\W]+/g,"") || "";
            };
            if(document.currentScript) return repStr(document.currentScript.src);

            //IE11没有了readyState属性, 也没有currentScript属性;
            // 参考 http://www.php.cn/
            var stack
            try {
                a.b.c() //强制报错,以便捕获e.stack
            } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
                stack = e.stack
                if (!stack && window.opera) {
                    //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
                    stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
                }
            }
            if (stack) {
                /**e.stack最后一行在所有支持的浏览器大致如下:
                 *chrome23:
                 * at http://www.php.cn/:4:1
                 *firefox17:
                 *@http://www.php.cn/:4
                 *opera12:http://www.php.cn/
                 *@http://www.php.cn/:4
                 *IE10:
                 *  at Global code (http://www.php.cn/:4:1)
                 *  //firefox4+ 可以用document.currentScript
                 */
                stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
                stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
                return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行号与或许存在的出错字符起始位置
            };

            //实在不行了就走这里;
            var node = getCurrentNode();
            //IE>=8的直接通过src可以获取,IE67要通过getAttriubte获取src;
            return repStr(document.querySelector ? node.src : node.getAttribute("src", 4)) || "";

            throw new Error("getCurrentPath error!");
        };

        var loadDpt = function(module) {
            var dp = "";
            for(var p =0; p<module.dps.length; p++) {
                //获取绝对的地址;
                var dp = getUrl(module.dps[p]);
                //如果依赖没有加载就直接加载;
                if( !modules[dp] ) {
                    loadScript(dp);
                };
            };
        };

        //主要的模块, 检测所有未加载的模块中未完成了的依赖是否加载完毕,如果加载完毕就加载模块, 如果加载过的话,而且所有依赖的模块加载完毕就执行该模块
        //而且此模块的exports为该模块的执行结果;
        var checkDps = function() {
            for(var key in modules ) {
                //初始化该模块需要的参数;
                var params = [];
                var module = modules[key];
                //加载完毕就什么都不做;
                if( module.state === "complete" ) {
                    continue;
                };
                if( module.state === "initial" ) {
                    //如果依赖没有加载就加载依赖并且modules没有该module就加载这个模块;
                    loadDpt(module);
                    module.state = "loading";
                };
                if( module.state === "loading") {
                    //如果这个依赖加载完毕
                    for(var p =0; p<module.dps.length; p++) {
                        //获取绝对的地址;
                        var dp = getUrl(module.dps[p]);
                        //如果依赖加载完成了, 而且状态为complete;;
                        if( modules[dp] && modules[dp].state === "complete") {
                            params.push( modules[dp].exports );
                        };
                    };
                    //如果依赖全部加载完毕,就执行;
                    if( module.dps.length === params.length ) {
                        if(typeof module.exports === "function"){
                            module.exports = module.exports.apply(modules,params);
                            module.state = "complete";
                            //每一次有一个模块加载完毕就重新检测modules,看看是否有未加载完毕的模块需要加载;
                            checkDps();
                        };
                    };
                };
            };
        };

        //[],fn; fn
        define = function(deps, fn, name) {
            if(typeof deps === "function") {
                fn = deps;
                deps = [];//我们要把数组清空;
            };

            if( typeof deps !== "object" && typeof fn !== "function") {
                alert("参数错误")
            };

            var src = getCurrentPath();
            //没有依赖, 没有加载该模块就新建一个该模块;
            if( deps.length===0 ) {
                modules[ src ] = {
                    name : name || src,
                    src : src,
                    dps : [],
                    exports : (typeof fn === "function")&&fn(),
                    state : "complete"
                };
                return checkDps();
            }else{
                modules[ src ] = {
                    name : name || src,
                    src : src,
                    dps : deps,
                    exports : fn,
                    state : "initial"
                };
                return checkDps();
            }
        };

        window.define = define;
        window.require = function() {
            //如果是require的话那么模块的名字就是一个不重复的名字,避免和define重名;
            window.define.apply([], Array.prototype.slice.call(arguments).concat( "module|"+setTimeout(function() {},0) ));
        };
        require.config = {
            base : getBasePath()
        };
        require.loadScript = loadScript;
        var loadDefaultJS = getCurrentNode().getAttribute("data-main");
        loadDefaultJS && loadScript(loadDefaultJS);
    })();
    </script>
</head>
<body>
</body>
</html>
登入後複製

從葉大大那邊偷的一個載入器, 這個載入器有點像jQuery中延遲對象($.Deferred)有關的方法when($.when)的實現;

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script>
        (function () {

            //存储已经加载好的模块
            var moduleCache = {};

            var define = function (deps, callback) {
                var params = [];
                var depCount = 0;
                var i, len, isEmpty = false, modName;

                //获取当前正在执行的js代码段,这个在onLoad事件之前执行
                modName = document.currentScript && document.currentScript.id || &#39;REQUIRE_MAIN&#39;;

                //简单实现,这里未做参数检查,只考虑数组的情况
                if (deps.length) {
                    for (i = 0, len = deps.length; i < len; i++) {
                        (function (i) {
                            //依赖加一
                            depCount++;
                            //这块回调很关键
                            loadMod(deps[i], function (param) {
                                params[i] = param;
                                depCount--;
                                if (depCount == 0) {
                                    saveModule(modName, params, callback);
                                }
                            });
                        })(i);
                    }
                } else {
                    isEmpty = true;
                }

                if (isEmpty) {
                    setTimeout(function () {
                        saveModule(modName, null, callback);
                    }, 0);
                }

            };

            //考虑最简单逻辑即可
            var _getPathUrl = function (modName) {
                var url = modName;
                //不严谨
                if (url.indexOf(&#39;.js&#39;) == -1) url = url + &#39;.js&#39;;
                return url;
            };

            //模块加载
            var loadMod = function (modName, callback) {
                var url = _getPathUrl(modName), fs, mod;

                //如果该模块已经被加载
                if (moduleCache[modName]) {
                    mod = moduleCache[modName];
                    if (mod.status == &#39;loaded&#39;) {
                        setTimeout(callback(this.params), 0);
                    } else {
                        //如果未到加载状态直接往onLoad插入值,在依赖项加载好后会解除依赖
                        mod.onload.push(callback);
                    }
                } else {

                    /*
                     这里重点说一下Module对象
                     status代表模块状态
                     onLoad事实上对应requireJS的事件回调,该模块被引用多少次变化执行多少次回调,通知被依赖项解除依赖
                     */
                    mod = moduleCache[modName] = {
                        modName: modName,
                        status: &#39;loading&#39;,
                        export: null,
                        onload: [callback]
                    };

                    _script = document.createElement(&#39;script&#39;);
                    _script.id = modName;
                    _script.type = &#39;text/javascript&#39;;
                    _script.charset = &#39;utf-8&#39;;
                    _script.async = true;
                    _script.src = url;

                    //这段代码在这个场景中意义不大,注释了
                    //      _script.onload = function (e) {};

                    fs = document.getElementsByTagName(&#39;script&#39;)[0];
                    fs.parentNode.insertBefore(_script, fs);

                }
            };

            var saveModule = function (modName, params, callback) {
                var mod, fn;

                if (moduleCache.hasOwnProperty(modName)) {
                    mod = moduleCache[modName];
                    mod.status = &#39;loaded&#39;;
                    //输出项
                    mod.export = callback ? callback(params) : null;

                    //解除父类依赖,这里事实上使用事件监听较好
                    while (fn = mod.onload.shift()) {
                        fn(mod.export);
                    }
                } else {
                    callback && callback.apply(window, params);
                }
            };

            window.require = define;
            window.define = define;

        })();
    </script>
</head>
<body>
</body>
</html>
登入後複製

一個例子

寫一個MVC的小例子,代碼簡單, 高手無視,  目錄結構如下:

我們把所有的事件放到了controller/mainController.js裡面,

define(["model/data","view/view0"],function(data, view) {
    var init = function() {
        var body = document.getElementsByTagName("body")[0];
        var aBtn = document.getElementsByTagName("button");
        for(var i=0; i< aBtn.length; i++) {
            aBtn[i].onclick = (function(i) {
                return function() {
                    body.appendChild( view.getView(data[i]) );
                };
            })(i);
        };
    };
    return {
        init : init
    };
});
登入後複製

把所有的資料放到了model/data.js裡面;

define(function() {
    return [
        {name : "qihao"},
        {name : "nono"},
        {name : "hehe"},
        {name : "gege"}
    ];
})
登入後複製

視圖的JS放到了view的目錄下,view0.js主要負責產生HTML字串或DOM節點;

define(function() {
    return {
        getView : function(data) {
            var frag = document.createDocumentFragment();
                frag.appendChild( document.createTextNode( data.name + " ") );
            return frag;
        }
    }
});
登入後複製

入口是app.js,他和load.html是同級目錄:

require(["controller/mainController"],function( controller ) {
    controller.init();
});
登入後複製

load.html這個是主介面:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title></head>
<body>
<button>0</button>
<button>1</button>
<button>2</button>
<button>3</button>
<script src="require.js" data-main="app.js"></script>
</body>
</html>
登入後複製

以上就是JavaScript 模組化程式設計載入器原理詳解的內容,更多相關內容請關注PHP中文網(www.php .cn)!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1421
52
Laravel 教程
1315
25
PHP教程
1266
29
C# 教程
1239
24
如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles