写点什么

javascript 中的模块系统

发布于: 2021 年 02 月 28 日

简介

在很久以前,js 只是简单的作为浏览器的交互操作而存在,一般都是非常短小的脚本,所以都是独立存在的。


但是随着现代浏览器的发展,特别是 nodejs 的出现,js 可以做的事情变得越来越多也越来越复杂。于是我们就需要模块系统来组织不同用途的脚本,进行逻辑的区分和引用。


今天将会给大家介绍一下 js 中的模块系统。


CommonJS 和 Nodejs

CommonJS 是由 Mozilla 公司在 2009 年 1 月份提出来了。没错,就是那个 firfox 的公司。


最初的名字叫做 ServerJS,在 2009 年 8 月的时候为了表示这个标准的通用性,改名为 CommonJS。


CommonJS 最主要的应用就是服务端的 nodejs 了。浏览器端是不直接支持 CommonJS 的,如果要在浏览器端使用,则需要进行转换。


CommonJS 使用 require()来引入模块,使用 module.exports 来导出模块。


我们看一个 CommonJS 的例子:


require("module"); require("../file.js"); exports.doStuff = function() {}; module.exports = someValue;
复制代码

注意,CommonJS 是同步加载的。


AMD 异步模块加载

AMD 的全称是 Asynchronous Module Definition 。它提供了一个异步加载模块的模式。


AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。


异步加载的好处就是可以在需要使用模块的时候再进行加载,从而减少了一次性全部加载的时间,尤其是在浏览器端,可以提升用户的体验。


看下 AMD 加载模块的定义:


 define(id?, dependencies?, factory);
复制代码

AMD 是通过 define 来定义和加载依赖模块的。


其中 id 表示要定义的模块的名字,dependencies 表示这个模块的依赖模块,factory 是一个函数,用来初始化模块或者对象。


我们看一个例子:


   define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {       exports.verb = function() {           return beta.verb();           //Or:           return require("beta").verb();       }   });
复制代码

这个例子中,我们定义了一个 alpha 模块,这个模块需要依赖”require”, “exports”, “beta”三个模块。


并且在 factory 中导出了 beta 模块的 verb 方法。


define 中 id 和 dependencies 都不是必须的:


//无id  define(["alpha"], function (alpha) {       return {         verb: function(){           return alpha.verb() + 2;         }       };   });
//无依赖 define({ add: function(x, y){ return x + y; } });
复制代码

甚至我们可以在 AMD 中使用 CommonJS:


   define(function (require, exports, module) {     var a = require('a'),         b = require('b');
exports.action = function () {}; });
复制代码

定义完之后,AMD 使用 require 来加载模块:


require([dependencies], function(){});
复制代码

第一个参数是依赖模块,第二个参数是回调函数,会在前面的依赖模块都加载完毕之后进行调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。


require(["module", "../file"], function(module, file) { /* ... */ }); 
复制代码

require 加载模块是异步加载的,但是后面的回调函数只会在所有的模块都加载完毕之后才运行。


CMD

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。它的全称是 Common Module Definition。


CMD 也是使用 define 来定义模块的,CMD 推崇一个文件作为一个模块:


define(id?, deps?, factory)
复制代码

看起来和 AMD 的 define 很类似,都有 id,依赖模块和 factory。


这里的 factory 是一个函数,带有三个参数,function(require, exports, module)


我们可以在 factory 中通过 require 来加载需要使用的模块,通过 exports 来导出对外暴露的模块,module 表示的是当前模块。


我们看一个例子:


// 定义模块  myModule.jsdefine(function(require, exports, module) {  var = require('jquery.js')('div').addClass('active');});
// 加载模块seajs.use(['myModule.js'], function(my){
});
复制代码

所以总结下 AMD 和 CMD 的区别就是,AMD 前置要加载的依赖模块,在定义模块的时候就要声明其依赖的模块。


而 CMD 加载完某个依赖模块后并不执行,只是下载而已,只有在用到的时候才使用 require 进行执行。


ES modules 和现代浏览器

ES6 和现代浏览器对模块化的支持是通过 import 和 export 来实现的。


首先看下 import 和 export 在浏览器中支持的情况:




首先我们看下怎么使用 export 导出要暴露的变量或者方法:


export const name = 'square';
export function draw(ctx, length, x, y, color) { ctx.fillStyle = color; ctx.fillRect(x, y, length, length);
return { length: length, x: x, y: y, color: color };}
复制代码

基本上,我们可以使用 export 导出 var, let, const 变量或者 function 甚至 class。前提是这些变量或者函数处于 top-level。


更简单的办法就是将所有要 export 的放在一行表示:


export { name, draw, reportArea, reportPerimeter };
复制代码

export 实际上有两种方式,named 和 default。上面的例子中的 export 是 named 格式,因为都有自己的名字。


下面看下怎么使用 export 导出默认的值:


// export feature declared earlier as defaultexport { myFunction as default };
// export individual features as defaultexport default function () { ... } export default class { .. }
复制代码

named 可以导出多个对象,而 default 只可以导出一个对象。


导出之后,我们就可以使用 import 来导入了:


import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
复制代码

如果导出的时候选择的是 default,那么我们在 import 的时候可以使用任何名字:


// file test.jslet k; export default k = 12;
// some other fileimport m from './test'; // 因为导出的是default,所以这里我们可以使用import m来引入console.log(m); // will log 12
复制代码

我们可以在一个 module 中使用 import 和 export 从不同的模块中导入,然后在同一个模块中导出,这样第三方程序只需要导入这一个模块即可。


export { default as function1,         function2 } from 'bar.js';
复制代码

上面的 export from 等价于:


import { default as function1,         function2 } from 'bar.js';export { function1, function2 };
复制代码

上面的例子中,我们需要分别 import function1 function2 才能够使用,实际上,我们可以使用下面的方式将所有的 import 作为 Module 对象的属性:


import * as Module from './modules/module.js';
Module.function1()Module.function2()
复制代码

然后 function1,function2 就变成了 Module 的属性,直接使用即可。


在 HTML 中使用 module 和要注意的问题

怎么在 HTML 中引入 module 呢?我们有两种方式,第一种是使用 src 选项:


<script type="module" src="main.js"></script>
复制代码

第二种直接把 module 的内容放到 script 标签中。


<script type="module">  /* JavaScript module code here */</script>
复制代码

注意,两种 script 标签的类型都是 module。


在使用 script 来加载 module 的时候,默认就是 defer 的,所以不需要显示加上 defer 属性。


如果你在测试的时候使用 file:// 来加载本地文件的话,因为 JS 模块安全性的要求,很有可能得到一个 CORS 错误。


最后,import() 还可以作为函数使用,来动态加载模块:


squareBtn.addEventListener('click', () => {  import('./modules/square.js').then((Module) => {    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');    square1.draw();    square1.reportArea();    square1.reportPerimeter();  })});
复制代码

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/js-modules/

本文来源:flydean 的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


发布于: 2021 年 02 月 28 日阅读数: 15
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
javascript中的模块系统