搜索
首页web前端js教程JavaScript 模块化编程之加载器原理详解

世面上有好多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
JavaScript框架:为现代网络开发提供动力JavaScript框架:为现代网络开发提供动力May 02, 2025 am 12:04 AM

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

JavaScript,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

node.js流带打字稿node.js流带打字稿Apr 30, 2025 am 08:22 AM

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python vs. JavaScript:性能和效率注意事项Python vs. JavaScript:性能和效率注意事项Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript的起源:探索其实施语言JavaScript的起源:探索其实施语言Apr 29, 2025 am 12:51 AM

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

幕后:什么语言能力JavaScript?幕后:什么语言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。

Python和JavaScript的未来:趋势和预测Python和JavaScript的未来:趋势和预测Apr 27, 2025 am 12:21 AM

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

See all articles

热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

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境