官方解析
JS 模块化是指将一个 JS 应用拆分成多个模块或文件,并通过一定的规范或语法定义它们之间的依赖关系,从而达到复用、维护、扩展的目的。常见的 JS 模块化方案有以下几种:
-
CommonJS:一种用于服务器端的模块化规范,主要用于 Node.js,它通过 require 来引入模块,通过 module.exports 或 exports 来导出模块。
-
AMD:一种用于浏览器端异步加载模块的规范,主要用于 RequireJS,它通过 define 来定义模块,通过 require 来异步加载模块。
-
CMD:一种用于浏览器端延迟执行的模块化规范,主要用于 SeaJS,它通过 define 来定义模块,通过 require 来延迟执行模块。
-
ES Module:一种官方规范,用于浏览器端和 Node.js 中,通过 import 来引入模块,通过 export 来导出模块。
这些模块化方案在实现上有不同的语法和规范,但它们都是为了实现 JS 的模块化而产生的,都可以使 JS 应用更易于维护和扩展。
其中,ES Module 是官方规范,具有更好的兼容性和可扩展性,而 CommonJS 和 AMD/CMD 则更多地用于 Node.js 和浏览器端的异步加载,各有优缺点。
模块化是一种管理代码的方案,它的思想是将一个复杂的程序按功能的不同划分成不同的模块(文件),各个模块存储自己的私有数据,外部不可访问,但同时模块也会向外暴露一些公共接口,使得多个模块之间可以互相通信。采用模块化方案能让开发者对程序的各项功能有更加清晰的认识,从而方便对代码进行维护和管理,提高开发效率。JS 的模块化在经历了多年发展后,最终形成了统一的规范,如今比较常见的有:
CommonJS:
Node.js 实现了 CommonJS 规范,在 Node.js 中,每个文件就是一个模块,有自己的作用域,也就是说,在一个文件里定义的变量或函数,都是私有的,对其他文件不可见。如果想提供给其它文件使用,可将它们挂载到 exports 或 module.exports 接口对象上,比如:
exports.a = 1;
// 等价于
module.exports.a = 1;
console.log(exports === module.exports); // true
其中,module 是一个对象,代表当前模块,它有一个属性 exports ,是对外的接口,所以 module.exports 和 exports 保存的是同一个引用。
如果要获取另一个模块暴露的接口,则使用 require(xxx) ,如果是第三方模块,xxx 为模块名;如果是自定义模块,xxx 为模块文件路径。require() 会读入并执行一个 JS 文件,然后返回该模块的module.exports 对象,如果没有发现指定模块,会报错。
-
AMD:AMD 是一种异步模块化方案,主要用于浏览器端。它采用异步加载模块的方式,可以在页面加载时并行加载多个模块,提高了页面的加载速度。AMD 的模块化语法主要是通过 define 和 require 来实现的,最著名的实现是 RequireJS。
-
CMD:CMD 是另一种异步模块化方案,也是主要用于浏览器端的。它采用异步加载模块的方式,但是与 AMD 不同的是,CMD 是按需加载模块,只有在需要使用某个模块时才会加载。CMD 的模块化语法主要是通过 define 和 require 来实现的,最著名的实现是 SeaJS。
-
ES6 Module:ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,极大可能成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
ES6 Module 与 CommonJS 的差异:
-
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
-
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
-
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
-
-
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
-
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
-
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
-
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。