模块化
无模块化标准阶段
早在模块化标准还没有诞生的时候,前端领域已经产生了一些模块化的开发手段,如文件划分
、命名空间
和 IIFE 私有作用域
。
(1)文件划分:
文件划分方式是最原始的模块化实现,简单来说就是将应用的状态和逻辑分散到不同的文件中,然后通过 HTML 中的
script
来一一引入。
问题:
- 模块的变量相当于全局声明和定义,会出现
命名冲突
的问题 - 由于变量都在全局定义,我们很难知道某个变量到底属于哪个模块,造成了
调试困难
- 无法清晰的管理模块之间的
依赖关系
和加载顺序
,加载顺序只能通过手动调整script标签的位置实现。
(2)命名空间:
命名空间
是模块化的另一种实现手段,它可以解决上述文件划分方式中全局变量定义
所带来的一系列问题。通过将每个模块的生命周期指定一个所属的命名空间,达到解决变量污染的问题,具体的实现是在每个模块包裹上windown.命名空间
(3)IIFE(立即执行函数):
相比于
命名空间
的模块化手段,IIFE
实现的模块化安全性要更高,对于模块作用域的区分更加彻底。
我们知道,每个IIFE
即立即执行函数
都会创建一个私有的作用域,在私有作用域中的变量外界是无法访问的,只有模块内部的方法才能访问(闭包)。
但实际上,无论是命名空间
还是IIFE
,都是为了解决全局变量所带来的命名冲突及作用域不明确的问题,,而并没有真正解决另外一个问题——模块加载。如果模块间存在依赖关系,那么 script 标签的加载顺序就需要受到严格的控制,一旦顺序不对,则很有可能产生运行时 Bug。
常见的模块规范
CommonJS 规范
CommonJS
是社区最早正式提出和维护的 JavaScript 模块规范,主要用于服务端
,随着 Node.js 越来越普及,这个规范也被业界广泛应用。对于模块规范而言,一般会包含 2 方面内容:
- 统一的模块化代码规范
- 实现自动加载模块的加载器(也称
loader
)
在代码中我们使用require
来引入其它模块,使用modlue.export
来导出一个模块,NodeJS内部会有相应的loader
来转移模版代码。
看样子CJS实现了上面提到的两点内容,但实际上,还是存在着一些问题。
- CJS强依赖于nodejs环境,浏览器无法直接识别运行cjs的代码,虽然社区推出了如
Browserify
等打包工具来支持打包CJS模块。 - CJS本身约定以同步的方式进行模块加载,这种加载机制放在服务端是没问题的,首先是模块都在本地,不需要进行网络IO,二来只有服务启动时才会加载模块,服务启动后会一直运行,所以对性能没有太大影响。但是放在浏览器环境下,大量的模块同步加载请求,会导致
浏览器JS解析过程阻塞
,导致页面加载速度缓慢。
为了解决同步加载的性能问题,社区又推出了AMD规范
AMD 规范
AMD
全称为Asynchronous Module Definition
,即异步模块定义规范。模块根据这个规范,在浏览器环境中会被异步加载,而不会像 CommonJS 规范进行同步加载,也就不会产生同步请求导致的浏览器解析过程阻塞的问题了。
在 AMD 规范当中,我们可以通过 define 去定义或加载一个模块, 如果模块需要导出一些成员需要通过在定义模块的函数中 return 出去。
也可以使用 require 关键字来加载一个模块,不过 require 与 define 的区别在于前者只能加载模块,而不能定义一个模块
。
和CJS一样,AMD同样无法直接在浏览器上运行,因为他不属于ECMA制定的语法,他需要由loader进行转换后才能在浏览器运行,常用的打包工具是requireJS
库,它完整的实现了AMD规范,至今还有不少项目在使用。
不过 AMD 规范使用起来稍显复杂,代码阅读和书写都比较困难。因此,这个规范并不能成为前端模块化的终极解决方案,仅仅是社区中提出的一个妥协性的方案,关于新的模块化规范的探索,业界从仍未停止脚步。
同期出现的规范当中也有CMD 规范
,这个规范是由淘宝出品的SeaJS
实现的,解决的问题和 AMD 一样。不过随着社区的不断发展,SeaJS 已经被requireJS
兼容了。
当然,你可能也听说过
UMD
(Universal Module Definition)规范,其实它并不算一个新的规范,只是兼容 AMD 和 CommonJS 的一个模块化方案,可以同时运行在浏览器和 Node.js 环境。顺便提一句,后面将要介绍的 ES Module 也具备这种跨平台的能力。
ES6 Module
ES6 Module
也被称作ES Module
(或ESM
), 是由 ECMAScript 官方提出的模块化规范,作为一个官方提出的规范,ES Module
已经得到了现代浏览器的内置支持。在现代浏览器中,如果在 HTML 中加入含有type="module"
属性的 script 标签,那么浏览器会按照 ES Module 规范来进行依赖加载和模块解析, 同时Node.js从12.20
版本开始正式支持原生 ES Module。也就是说,如今 ES Module 能够同时在浏览器与 Node.js 环境中执行,拥有天然的跨平台能力。