1. 为什么需要前端模块化?
js在设计之初为了简单化,并没有加入模块化的设计。而随着js有能力构建成大型项目,模块化就是不可或缺的一环了,因为模块化有助于提高项目的可维护性,复用性。直到2015年ES6的发布,官方才推出了模块化的能力。在此之前,社区出现了commonjs,amd,cmd等模块化规范。虽然amd,cmd已经退出历史舞台,但commonjs,依然顽强存在于nodejs中,与esm共存。接下来,将对几种模块化机制做简单介绍。
2. 模块化实现方式
2.1 commonjs
commonjs由于被nodejs运用,得以沿用至今。
基本语法:
需要使用reqire()指定依赖,而使用exports对象定义暴露的api
const moduleB = require('./moduleB')
module.exports={
stuff: moduleB.doStuff()
}
实现原理:
// require函数的伪代码
function require(path){
if(该模块有缓存吗){
return 缓存结果;
}
function _run(exports, require, module, __filename, __dirname){
// 模块代码会放到这里
}
var module = {
exports: {}
}
_run.call(
module.exports,
module.exports,
require,
module,
模块路径,
模块所在目录
);
把 module.exports 加入到缓存;
return module.exports;
}
此模块化只能用于node环境,不能用于浏览器端。为什么不能用在浏览器端?这是因为
- 浏览器要加载JS文件,需要远程从服务器读取,而网络传输的效率远远低于node环境中读取本地文件的效率。由于CommonJS是同步的,这会极大的降低运行性能。
- 如果需要读取JS文件内容并把它放入到一个环境中执行,需要浏览器厂商的支持,可是浏览器厂商不愿意提供支持,最大的原因是CommonJS属于社区标准,并非官方标准
commonjs的特点:
- 实现缓存
- 同步加载
- 动态加载模块(模块是否执行,是在运行时确定的)
- 本质是函数的调用赋值
由于commonjs不能在浏览器端使用,因此,社区提出了AMD,CMD方案,但由于现在被ES6取代了,下面仅简单介绍。
2.2 AMD
AMD,全称Asynchronous Module Definition,异步模块定义,以浏览器为目标执行环境。让模块声明自己的依赖,运行在浏览器中的模块系统按需获取依赖,并在依赖加载完成后执行他们的依赖。它允许在浏览器中异步加载模块。
基本语法
导入和导出模块的代码,都必须放置在define函数中
define([依赖的模块列表], function(模块名称列表){
//模块内部的代码
return 导出的内容
})
2.3 CMD
全称是Common Module Definition,公共模块定义规范。为了统一commonjs和amd生态而提出的。可用于创建这两个系统都可以使用的模块代码。
基本语法
在CMD中,导入和导出模块的代码,都必须放置在define函数中
define(function(require, exports, module){
//模块内部的代码
})
2.4 ESM(ES6 module)
esm 是ECMASCRIPT官方提出的模块化方案,可以说是对之前模块化方案的融合改进。
基本语法:采用import和export关键字
特点
- 模块只在加载后执行
- 模块只能加载一次
- 所有环境均支持:node和浏览器环境
- 同时支持静态依赖和动态依赖 静态依赖:在代码运行前就要确定依赖关系
- 依赖是异步加载和执行的
- 符号绑定:导入位置的符号和导出的符号并非赋值,它们指向的是一个内容
进一步思考:
出现ESM之后,浏览器已经支持模块化了,能否用ESM来解决前端工程化的问题。换句话说,浏览器端能否全部采用ESM进行模块化?
参考资料:
- 《JavaScript高级程序设计》
- 阮一峰-ES6模块化