最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript中的模块系统

    正文概述 掘金(李唐敏名)   2021-03-10   610

    模块化编程,是强调将计算机程序的功能分离成独立的、可相互改变的“模”的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面所必需的所有东西。通过模块化编程,我们可以进行模块复用,从而减少重复代码,同时,降低了代码耦合,方便调试和维护。

    ES6之前

    如果不使用模块,会出现相同变量名的覆盖等问题。

    例如,有如下代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
    </body>
    <script src="./add1.js"></script>
    <script src="./add2.js"></script>
    <script src="./main.js"></script>
    </html>
    
    // add1.js
    var a = 1;
    function add1(num){  	// 返回 输入 + 1
        return a + num;
    }
    console.log(add1(1)); 
    // add2.js
    var a = 2;
    function add2(num){ 	// 返回 输入 + 2
        return a + num;
    }
    console.log(add2(2)); 
    // main.js
    console.log(add1(3));	// 希望返回 4
    

    前两个js文件中定义了两个函数,add1add2 ,功能就是分别返回输入值加1、加2。

    在html文件中,依次导入了这三个js文件。结果,main.js 打印4,结果打印了5。

    这是因为,js是在全局环境中依次执行代码的,a的值被重新赋值。

    立即执行函数

    上面代码之所以出现这种情况,是因为,变量a 定义在全局作用域里。在ES6之前,除了全局作用域,还有函数作用域,借助函数作用域,我们可以实现初版的模块系统。

    // add1.js
    var add1 = (function(){ // 新加代码
        var a = 1;
        function add1(num){  	// 返回 输入 + 1
            return a + num;
        }
    	console.log(add1(1)); 
        
        return add1 // 新加代码
    })() // 新加代码
    
    // add2.js
    var add2 = (function(){ // 新加代码
        var a = 2;
        function add2(num){ 	// 返回 输入 + 2
            return a + num;
        }
        console.log(add2(2)); 
        
        return add2 // 新加代码
    })() // 新加代码
    
    
    // main.js
    console.log(add1(3));	// 希望返回 4
    

    这里,不再将代码直接执行,而是将代码放到了一个立即执行函数中,并返回了定义的函数。

    这样,就可以通过立即执行函数返回值来访问到定义函数

    原理:闭包。

    CommonJS

    CommonJS是提供给服务器端使用的规范。例如,使用Node.js开发一个服务器程序,就可以使用CommonJS 规范,因为Node.js实现了这一规范。

    语法require()指定依赖,使用exports 对象定义自己的公共API

    // ./moduelA.js
    var moduleA = require('./moduleB') // 获取依赖
    var add = moduleA.add
    
    module.exports = { // 定义API 
        add:add,  
    }
    

    值得注意的是,这段代码是同步运行的,因为CommonJS 是为服务器设计的,所以没有考虑网络延时。

    原理:

    每一个module都是一个Module实例。

    module之间,通过parent children ,形成依赖图。

    而在,require中,有一个cache对象用来存储加载的模块id->module字典。执行require()方法时,会从cache取值。

    AMD

    CommonJS 以服务器端为目标环境,能够一次性把所有模块都加载到内存,而异步模块定义(AMD,Asynchronous Module Definition)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。

    由于AMD不是ES规范,所以是不能直接在浏览器中使用的,需要借助第三方包require.js

    // src/math.js
    // 定义模块
    define('math', function () { // 第二个参数如果是依赖列表,回调函数可以通过入参获取模块
        'use strict';
        let add = function (x, y) {
            return x + y;
        };
        return {
            add: add, 
        }
    });
    // src/index.js
    // 使用模块
    require(['math'], function (math) {
        alert(math.add(1, 1));
    })
    
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
    </body>
    <script src="./js/require.js" async="true" data-main="./src/index.js"></script>
    <-- 加载require工具包,并设置./src/index.js为入口文件 -->
    </html>
    

    运行过程:

    1. ./js/require.js加载完后,会去请求./src/index.js
    2. index.js文件接收到后,开始执行,发现需要模块math,于是创建一个异步函数,去请求math.js文件。
    3. math.js文件接收到后,开始执行。define方法检查参数,发现没有依赖,于是执行函数,并将返回值(模块api)与math绑定。
    4. 执行回调函数。输入值为过程3返回值(模块api)。

    原理:

    1. require: 获取依赖后执行函数。
    2. define: 用来定义模块,模块与模块间的依赖关系。

    ES6模块

    ECMAScript 6 模块是作为一整块JavaScript 代码而存在的。带有type="module"属性的

    <script type="module">
    </script>
    
    <script type="module" src="path/to/myModule.js"></script>
    

    原理:原生支持。

    webpack与模块系统

    不知道大家在开发项目时有没有发现,我们写代码时用的时ES6的模块。但是使用webpack打包后,似乎并没有使用type="module"属性

    JavaScript中的模块系统

    或许我们应该分析打包结果来进一步了解webpack。

    我们创建一个简单的项目。

    main.js里分别使用ES6的模块和cms模块写法,调用了两个对应的文件,并执行了输出。配置文件指定main.js为入口,dist/js/main.js为出口。

    执行:webpack --config ./webpack.config.js 进行打包。

    dist/js/main.js中,我们可以看到打包结果。下面,开始逐段分析打包结果。

    总体

    (() => {
    	// 段落1
        // 段落2
        // 段落3
        // 段落4
    })();
    

    代码总体结构是一个立即执行函数,这是为了避免污染全局变量。

    段落1

    var __webpack_modules__ = ({
      "./src/add-cs.js":
        ((module) => {
          eval(eval1);
        }),
    
      "./src/add-es.js":
        ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
          "use strict";
          eval(eval2);
        }),
    
      "./src/main.js":
        ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
          "use strict";
          eval(eval3);
        })
    });
    
    var __webpack_module_cache__ = {};
    
    

    这一段定义了两个变量:

    __webpack_modules__ 模块字典,键为模块路径,值为一个函数,函数后面进行分析。

    __webpack_module_cache__ 模块缓存字典。

    段落2

    function __webpack_require__(moduleId) {
      if (__webpack_module_cache__[moduleId]) {  // 如果 缓存里 有 id为moduleId 的模块
        return __webpack_module_cache__[moduleId].exports;    // 从 缓存字典中返回  exports
      }
      var module = __webpack_module_cache__[moduleId] = {  // 否则, 使用id定义缓存字典,exports为空
        exports: {}
      };
    
      __webpack_modules__[moduleId](module, module.exports, __webpack_require__)
      return module.exports;
    }
    

    这里定义了__webpack_require__函数,函数接收模块id,返回值为模块对象的exports属性。

    1. 对于存在__webpack_module_cache__中的模块,直接返回exports属性。
    2. 对于不在__webpack_module_cache__中的模块,在__webpack_module_cache__中定义一个空对象module,并module作为参数,执行__webpack_modules__对应moduleId的函数。最后,返回对象module.exports

    段落3

    (() => {
      __webpack_require__.d = (exports, definition) => { 
        for (var key in definition) {
          if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
            Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
          }
        }
      };
    })();
    
    (() => { 
      __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
    })();
    
    (() => { // r函数 标记函数:接收 exports 对象。 在 exports 对象上添加属性 __esMoudle 为 true 
      __webpack_require__.r = (exports) => {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
          Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(exports, '__esModule', { value: true });
      };
    })();
    

    这里使用立即执行函数,在__webpack_require__上定义了三个函数d o r

    d 将对象definition上的属性,定义在exports 上。

    o 判断对象obj上,是否存在属性prop

    r 给对象exports添加属性Symbol.toStringTag__esModule

    段落4

    var __webpack_exports__ = __webpack_require__("./src/main.js");
    

    调用__webpack_require__

    eval

    通过上述代码分析,我们知道了webpack通过__webpack_require__ 加载依赖,对于不在__webpack_module_cache__中的模块回去执行__webpack_modules__里的方法,而这些方法使用eval执行了一串代码串。下面,我将代码串改成了容易看懂的格式。

    首先,我们需要明确,这三段代码有输入参数:

    __unused_webpack_module__ module对象 值为{exports:{}}

    __webpack_exports__ __unused_webpack_module__exports属性

    __webpack_require__ 函数__webpack_require__

    // **eval1**
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(
        __webpack_exports__,
        {
            "add": () => (add)
        }
    );
    function add(a, b) {
        return "es module:" + (a + b);
    }
    //# sourceURL=webpack://webpacktest/./src/add-es.js?
    
    // **eval2**
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(
        __webpack_exports__,
        {
            "add": () => (add)
        }
    );
    function add(a, b) {
        return "es module:" + (a + b);
    }
    //# sourceURL=webpack://webpacktest/./src/add-es.js?"
    

    这两段代码做的几乎一样,但是,我们的原代码的模块导出不见了。

    可以推测,webpack对代码进行了分析,将模块导出部分,重构成了d函数的实现。

    // **eval3**
    __webpack_require__.r(__webpack_exports__);
    var _add_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/add-es.js");
    let add_cs = __webpack_require__("./src/add-cs.js");
    
    console.log((0, _add_es__WEBPACK_IMPORTED_MODULE_0__.add)(1, 2))
    console.log(add_cs.add(3, 4))
    //# sourceURL=webpack://webpacktest/./src/main.js?
    

    第三段代码也就顺理成章了,模块导入的代码被替换为__webpack_require__函数。

    所以webpack的模块系统是这样工作的。

    1. 分析原代码,将原代码中的模块导入都替换为__webpack_require__函数,导出替换为d函数。
    2. 替换完成后,生成了代码串,根据文件路径放到__webpack_modules__ 中。

    起源地下载网 » JavaScript中的模块系统

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元