前言
前端模块化发展了10余年左右,前前后后出现了commonJS,AMD,CMD等多种模块化标准方案,目前最新的是ES module。下面大概讲述一下这十年左右的发展历程~
一、理解前端模块化
(一)含义
最开始JS是没有模块化的,由于前端页面越来越复杂,项目越来越大,催生了模块化的概念。
前端模块化是一种开发管理规范,用分割模块的方式来管理优化代码。每个模块有它特定的功能,模块间相互隔离,但可以通过特定接口访问内部成员,也可以依赖其他模块。
(二)为什么要使用模块化?
(1)在没有模块化时:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script>
console.log(2)
</script>
</body>
</html>
我们所有的业务代码,都通过script
标签内嵌到HTML中,代码质量随着业务的扩大变得无法保证。
(2)最初的模块化方案是:
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="a.js"></script>
<script src="b.js"></script>
<script src="index.js"></script>
</body>
</html>
将代码写在外部JS文件中,一个JS文件为一个模块,通过script src
引入。这样组织代码带来了一些好处:
- 提高了代码复用性,通过代码可以抽离
- 提高代码可维护性
- 灵活架构,焦点分离,方便模块间组合、分解
虽然这样可以将不同的功能模块分割,但明显不是很好的解决方案,仍然存在一些弊端:
- 作用域:所有JS文件模块共用一个作用域-全局作用域,会有命名冲突,污染全局的问题。
- 加载顺序:模块若有依赖模块,必须按特定的顺序加载JS文件。被依赖的先加载。
- 文件数量很多
(3)为了解决作用域的问题,之后出现了闭包立即执行函数
来定义一个模块:
;(function(){...})()
立即执行函数的作用域是独立的,这样就避免了上面存在的污染全局的问题。但模块依赖的加载顺序问题依然存在。
因此,我们需要比较标准的模块化方案来解决这些问题。
二、前端模块化标准规范
(一)CommonJS
随着V8引擎的诞生,诞生了Node,Node 应用采用的是 CommonJS 模块规范。此规范特点:
- 每个文件就是一个模块,有自己的作用域。
- node环境使用,模块的加载是运行时同步加载的;
require
引入,module.exports
或者exports
导出(输出的是值的拷贝)- 缓存机制:每个模块可以多次加载但是只会在第一次加载时运行,然后会被缓存供后续加载时使用
由于此规范无法在浏览器环境中使用,所以之后又催生了一些适用浏览器环境的规范,AMD CMD。
(二)AMD
当然AMD只是一种规范,实现此规范的库是RequireJS,特点:
- async module definition 异步模块定义,异步加载,适应环境
define(moduleName, [module], factory)
定义模块,一个文件可设置多个模块require([module], callback)
引入模块- 模块依赖前置
(三)CMD
之后阿里也对模块化做了贡献,推出了符合CMD规范的SeaJS库(现已不再维护),特点:
- common module definition 通用模块规范,与AMD类似,同样适应浏览器环境
define(function(require, exports, module){})
定义模块seajs.use([module路径],function(moduleA,moduleB,moduleC){ })
使用模块- 遵循模块依赖就近原则,按需加载,这是与AMD(依赖前置)的本质区别。对模块化做的改进之处。
(四)ES module
CommonJS AMD CMD 都不是ES官方给出的规范,直到2015年推出了ES6版本,从语言标准层面实现模块功能,作为前后端通用的解决方案。特点:
- ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
- 浏览器、node通用
- ES6模块默认是严格模式
- 一个文件一个模块
import
导入模块export
导出模块
浏览器端使用ES6模块:
对于type="module"
的<script>
,浏览器会执行异步加载,不会阻塞浏览器。
<script type="module" src="a.js"></script>
由于ES6+语法在浏览器和node(node13+支持,package.json
配置"type":"module"
)支持方面还不是很友好,通常需要babel
才能使用。