资讯专栏INFORMATION COLUMN

简单实现 JavaScript 模块加载

tigerZH / 1742人阅读

摘要:语言官方未实现命名空间,我们定义一个函数以实现命名空间。函数的使用如我们可以这样实现对象保存着所有已加载的模块每一个模块实例都有属性作为时查找的标识符,属性作为对外暴露的对象,属性表示是否加载完。将函数传入,来支持模块内引用其他模块。

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;
        };
    }
]);

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95627.html

相关文章

  • 【Node】前后端模块规范与模块加载原理

    摘要:例如指定一些依赖到模块中实现规范的模块化,感兴趣的可以查看的文档。 CommonJS 定义了 module、exports 和 require 模块规范,Node.js 为了实现这个简单的标准,从底层 C/C++ 内建模块到 JavaScript 核心模块,从路径分析、文件定位到编译执行,经历了一系列复杂的过程。简单的了解 Node 模块的原理,有利于我们重新认识基于 Node 搭建的...

    jsyzchen 评论0 收藏0
  • 前端性能优化(三)——传统 JavaScript 优化的误区

    摘要:二模块化误区加快加载和执行的速度,一直是前端优化的一个热点。结果文件减少,也达到了预期的效果。避免不必要的延迟。最后再根据文件的功能类型,来决定是放在页面的头部还是尾部。 注:本文是纯技术探讨文,无图无笑点,希望您喜欢 一.前言 软件行业极其缺乏前端人才这是圈内的共识了,某种程度上讲,同等水平前端的工资都要比后端高上不少,而圈内的另一项共识则是——网页是公司的脸面! 几年前,谷歌的一项...

    UsherChen 评论0 收藏0
  • JavaScript模块化编程探索

    摘要:模块化编程,已经成为一个迫切的需求。随着网站功能逐渐丰富,网页中的也变得越来越复杂和臃肿,原有通过标签来导入一个个的文件这种方式已经不能满足现在互联网开发模式,我们需要团队协作模块复用单元测试等等一系列复杂的需求。 随着网站逐渐变成互联网应用程序,嵌入网页的Javascript代码越来越庞大,越来越复杂。网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发...

    jayzou 评论0 收藏0
  • Node.js随手笔记(一):node简介与模块系统

    摘要:模块系统为了让的文件可以相互调用,提供了一个简单的模块系统。但是,没有模块系统。包管理简称,是随同一起安装的包管理工具。输入命令,根据提示配置包的相关信息,生成相应的。以上所描述的模块载入机制均定义在模块之中。 Node.js简介 首先从名字说起,网上查阅资料的时候会发现关于node的写法五花八门,到底哪一种写法最标准呢?遵循官方网站的说法,一直将项目称之为Node或者Node.js。...

    TNFE 评论0 收藏0
  • 浅谈 JavaScript 模块化编程

    摘要:与在模块化编程的世界中,有两个规范不得不提,它们分别是和。所有依赖于某个模块的代码全部移到模块加载语句的回调函数中去。的语句接受两个参数在回调函数中,可以通过变量引用模块。回调函数的返回值就是当前对象的导出值。 JavaScript本身不是一种模块化语言,设计者在创造JavaScript之初应该也没有想到这么一个脚本语言的作用领域会越来越大。以前一个页面的JS代码再多也不会多到哪儿去,...

    wdzgege 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<