前端模块化
前端模块化——CommonJS、AMD、CMD、ESM
前端模块化的概念:
前端最初是没有模块化的,都是通过script引入,然后这个script引入的文件就会被全部加载,开发者需要看这个完整的script,才能知道这个文件的作用。
模块化就是一个单独的script文件就是一个独立的模块,通过向外暴露特定的导出变量和函数,开发者不用关心文件里的其它内容是怎么实现的,提高了代码的复用率和开发效率。模块化的基础是js函数。
前端模块化的发展历程:
前端没有模块化-nodejs出现(node使用commonjs实现前端模块化)-CommonJS用同步的方式加载模块,在浏览器里限于网络原因,就需要使用异步加载。所以出现了AMDjs和CMD(他们都是为了解决异步加载的问题)-后来的ESM,新的浏览器标准,之前的amd和cmd只是补丁,esm是重新定义了标准,成为浏览器和服务器通用的模块解决方案其模块功能主要由两个命令构成:export
和 import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
结语 commonjs是旧的方式,amd和cmd是commonjs的补丁。esm是新的标准,未来是esm的。
commonjs
commonjs用同步的方式加载模块。主要是在node里使用,是服务器端使用方法。有四个重要的环境变量为模块化的实现提供支持:module
、exports
、require
、global
。实际使用时,用 module.exports
定义当前模块对外输出的接口(不推荐直接用 exports
),用 require
加载模块。
exports
和 module.export
区别:
module.export: 每个模块内部,module变量代表当前模块,这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实就是加载该模块的exports属性。既可以使用 .
语法,也可以使用 =
直接赋值。
exports:为了方便,node为每个模块提供了一个exports变量,指向module.exports。这等同于在每个模块头部,有这么一行代码:
var exports = module.exports;
所以不能直接将exports指向一个值,这会切断 exports 与 module.exports 的联系(但是可以用module.exports来指向一个值)
./demo.js
var age = 7;
var sayHello= function (name) {
return "hello " + name;
};
exports = age; //不要这么干。这么做会切断exports与module.exports的联系
AMD
CommonJS用同步的方式加载模块。在服务端,模块文件都存放在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用 require.config()
指定引用路径等,用 definde()
定义模块,用 require()
加载模块。
首先我们需要引入require.js文件和一个入口文件main.js。main.js中配置 require.config()
并规定项目中用到的基础模块。
/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>
/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export
和 import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
总结
- CommonJs、AMD、CMD 是js模块化开发的规范,对应的实现是Node.js、require.js、sea.js
- CommonJs 主要针对服务端,AMD/CMD/ES Module主要针对浏览器端,容易混淆的是AMD/CMD。(顺便提一下,针对服务器端和针对浏览器端有什么本质的区别呢?服务器端一般采用同步加载文件,也就是说需要某个模块,服务器端便停下来,等待它加载再执行。这里如果有其他后端语言,如java。而浏览器端要保证效率,需要采用异步加载,这就需要一个预处理,提前将所需要的模块文件并行加载好。)
- AMD/CMD区别,虽然都是并行加载js文件,但还是有所区别,AMD是预加载,在并行加载js文件同时,还会解析执行该模块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成);而CMD是懒加载,虽然会一开始就并行加载js文件,但是不会执行,而是在需要的时候才执行。 (1)加载方式:AMD异步加载,CMD同步加载。 (2)依赖声明:AMD需要在模块定义时声明依赖,CMD可以在需要时再引入依赖。 (3)适用环境:AMD更适用于浏览器环境,CMD更适用于服务器端环境。 (4)实现工具:RequireJS是AMD规范的主要实现,而SeaJS是CMD规范的主要实现。
- AMD/CMD的优缺点.一个的优点就是另一个的缺点, 可以对照浏览。 AMD优点:加载快速,尤其遇到多个大文件,因为并行解析,所以同一时间可以解析多个文件。 AMD缺点:并行加载,异步处理,加载顺序不一定,可能会造成一些困扰,甚至为程序埋下大坑。 CMD优点:因为只有在使用的时候才会解析执行js文件,因此,每个JS文件的执行顺序在代码中是有体现的,是可控的。 CMD缺点:执行等待时间会叠加。因为每个文件执行时是同步执行(串行执行),因此时间是所有文件解析执行时间之和,尤其在文件较多较大时,这种缺点尤为明显。(PS:重新看这篇文章,发现这里写的不是很准确。确切来说,JS是单线程,所有JS文件执行时间叠加在AMD和CMD中是一样的。但是CMD是使用时执行,没法利用空闲时间,而AMD是文件加载好就执行,往往可以利用一些空闲时间)
- CommonJS 和 ES Module 区别:CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- 如何使用? CommonJs 的话,因为 NodeJS 就是它的实现,所以使用 node 就行,也不用引入其他包。 AMD则是通过
<script>
标签引入require.js, CMD则是引入sea.js