随着网站逐渐变成“
互联网应用程序”,嵌入网页的JavaScript代码越来越庞大,越来越复杂。
网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等... ...开发者不得不使用软件工程的方法,管理网页的业务逻辑。
JavaScript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
但是JavaScript不是一种模块化语言,它不支持“类”,更不要说是“模块”了。
1、原始写法
上面的m1()和m2()组成一个模块,使用的时候直接调用就好了。
但是,这种做大的缺点很明显:“污染”了全局变量,无法保证不与其他的模块变量名发生冲突,而且模块成员之间看不吃直接关系。
2、对象写法:
var module = new Object({
_count: 10,
m1: function () {
alert( "我是方法m1" );
},
m2: function () {
alert( "我是方法m2" );
}
});
函数m1() 和 m2()都封装在module对象里,使用的时候直接调用这个对象的属性。
缺点:这样的写法会暴露所有的模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module._count = 1000; //改变计数器的值
3、立即执行函数
(immediately-invoked function expression,IIFE)可以达到不暴露私有成员的目的。
使用上面的写法,外部代码无法直接读取内部的 _count 变量。
4、放大模式
上面这段代码为module1添加了一个新的方法m3(),然后返回新的module模块。
5、宽放大模式:(loose augmentation)
在浏览器环境中,模块的各个部分通常都是在网络上获取的,有时候无法知道哪个部分会先加载,如果采用上一节的写法,第一个执行的部分有可能加载一个不存在的空对象,这时候要采用“宽放大模式”。
与“放大模式”相比,“宽放大模式”就是“
立即执行函数”的参数可以是
空对象。
6、输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) {
//
})(jQuery, YAHOO);
上面的module1模块需要使用jQuery和YUI库,就把这两个库(其实就是两个模块)当做参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
7、模块的规范
为什么模块很重要?
因为有了模块,我们就可以方便的使用别人的代码,想要什么功能,就加载什么模块。
但是这样做需要有一个前提,那就是大家都必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不乱套了!考虑到JavaScript模块现在还没有官方规范,这一点更为重要。
目前通行的JavaScript模块有两种,CommonJS和AMD。
8、CommonJS
2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。
这标志“JavaScript模块化编程”正式诞生,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂度有限;但是在服务器端,一定要有模块,与操作系统和其他用应用程序互动,否则根本没办法编程。
node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性的方法require(),用于加载模块。假设有一个数学模块math.js,可以像下面这样加载。
var math = require(‘math’); //加载模块
math.add(2,3); //5 调用模块内方法
需要知道require()方法用于加载模块的作用。
9、浏览器环境
有了服务器模块后,很自然的,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。
但是,由于一个重大的局限,使得CommonJS规范不使用于浏览器。
问题来了,还是这段代码:
var math = require(‘math’); //加载模块
math.add(2,3); //5 调用模块内方法
运行到第二行的时候,必须等待math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里。也就是说,如果加载时间很久,整个应用就会停在那里等待。
这对服务器端不是问题,因为所有的模块都存在本地硬盘,可以同步加载完成,等待时间就是硬盘读取的时间。但是,对于浏览器这却是个大问题,因为模块都在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死”状态。
因此,浏览器端的模块,不能采用“同步加载”(synchronize),只能采用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。
10、AMD
AMD是“asynchronous Module Definition”的缩写,意思是“异步模块定义”。它采用异步方式加载模式,模块的加载不影响他后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,他要求两个参数:
require([module],callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改成AMD形式,就是下面这样:
require(['math'],function(math){
math.add(2,3);
});
math.add()与math模块加载不上同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
目前,主要有两个JavaScript库实现了AMD规范:require.js和curl.js。
参考博客: http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html