JavaScript模块化:从CommonJS到AMD
# 0.前言
本篇博客为阮一峰对JavaScript模块化的两篇文章的读书笔记。
# 1.模块化需要做到什么?
阮一峰在第一篇文章探讨的一个问题,模块化需要做到哪儿些要求?
主要需要满足几点要求:
- 不能"污染"全局变量,保证不与其他模块发生变量名冲突。
- 模块成员之间需要有关联。
如下面这个例子中,函数m1()
和m2()
简单组合,形成一个模块。
function m1(){
//...
}
function m2(){
//...
}
2
3
4
5
6
7
上面的代码就是最简单的模块化,但是存在几点问题:
1、变量存放在全局变量中,无法保证m1
和m2
变量名是否与外部变量重名。
2、无法按需导出,模块中的所有变量(Context
)被注入到全局环境中,无法保护私有变量的不被修改。
3、模块与模块之间无法做到关联,如A
模块的变量需要依赖于B
模块已经加载完成。
# 改进1:块级作用域
使用对象的方式,主动创建一个全新的作用域
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
2
3
4
5
6
7
8
9
10
11
12
调用时,就是调用这个对象的属性。
module1.m1();
缺点:会暴露所有的模块成员,内部状态可以被外部改写。如下:
module1._count = 5;
# 改进2:使用立即执行函数,形成一个闭包
原文:使用"立即执行函数 (opens new window)"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
闭包可以同时达到形成了一个全新的变量空间,以及按需暴露变量的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
但是,这种写法仍有缺陷,就是使用立即执行函数
形成的封闭的上下文(Context
),是一个全新的内存空间,模块化还需要接入其他模块的环境(Context
)的功能。
# 改进3:修改立即执行函数,使其接受参数
阮一峰将其称为:放大模式
与宽放大模式
// 1. 放大模式:
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
// 2. 宽放大模式:就是兼容性处理 || {}
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 技巧:导入全局变量,并重命名
下面的module1
模块需要使用jQuery
库和YUI
库,就把这两个库(其实是两个模块)当作参数输入module1
。
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
2
3
4
5
以上的写法:保证模块的独立性,还使得模块之间的依赖关系变得明显。
# 2. 从服务端到浏览器端的模块化演进
对于模块化而言,规范有很多,例如:CommonJS
、AMD
、CMD
、ES6
。区别主要在于几点:导入和导出。
导入的方式:可以是记录引用地址(ES6),也可以直接将环境全部存入内存中(require)。
导出的方式:需要将私有环境和导出的变量分区分开来,如上面介绍使用
立即执行函数+return
的导出方式。
不同模块化规范,其实对应着不同的使用环境。如:
CommonJs
:Node环境/服务端环境。AMD
:特点是模块的异步导入,特别适合需要HTTP请求的浏览器环境。CMD
:不清楚。ES6
:兼容服务器环境与浏览器环境。
下面的代码适合在服务端运行, 在浏览器端也可以运行,只不过代码同步执行的时间太久了(假死
状态)。
// CommonJS规范 在Node环境中执行下面这段代码没有问题
var math = require('math');
math.add(2,3); // 这段代码必须要等math.js加载完成后蔡奶奶个执行
2
3
由于服务器端math.js
就是存在本地硬盘,所以代码同步执行是没有问题的,但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
# AMD:服务端模块化
AMD
(opens new window)是"Asynchronous Module Definition
"的缩写,意思就是"异步模块定义"。
异步通过
回调函数
实现。回调函数的本质:提前定义,延时调用(函数的触发不由我们控制,而是等待程序执行后触发)。
AMD
也采用require()
语句加载模块,但是不同于CommonJS
,它要求两个参数:
require([module], callback);
第一个参数[module]
,是一个数组,里面的成员就是要加载的模块;第二个参数callback
,则是加载成功之后的回调函数。如果将前面的代码改写成AMD
形式,就是下面这样:
require(['math'], function (math) {
math.add(2, 3);
});
2
3
目前,主要有两个Javascript库实现了AMD规范:require.js (opens new window)和curl.js (opens new window)。