JS模块化编程

来源:互联网 发布:软件测试就业前景 编辑:程序博客网 时间:2024/04/29 03:06

1. 背景

  JS的强大已经不用解释了,从Web RIA到Node服务器,到处都是JS的身影。然而由于出身的缘故,JS本身在大规模应用上存在着很多问题,比如模块化编程。本文以浏览器端模块化编程为内容,重点介绍AMD规范和CMD规范。在服务器端,NodeJS遵守的Common规范我们这里暂不讨论。

  对于计算机语言,模块化编程是必不可少的,对架构设计、代码复用起到至关重要的作用,工程中引入别人写好的库和模块能大大缩减开发周期。C/C++中,我们可以用include;Java中我们可以用import。可是在JS中,这些是不存在的,取而代之的是script标签。

  在浏览器端,我们可以通过script标签引入需要的库,然后加载自己的脚本,最后运行脚本。这样做似乎与include和import没什么区别,然而有几个重要的因素必须要考虑:JS是解释型语言,边加载边运行,后续脚本运行时,这些脚本所依赖的一切必须已经加载完毕;JS脚本加载时会阻塞浏览器,如果加载的JS很多很大,浏览器会卡住,带来很差的用户体验;通过调整script标签顺序可以修改JS模块之间的依赖,然而当模块很多时,这种做法就不可用了。

  鉴于以上,JS模块化编程规范应运而生,并出现了大量实现。这些实现,简单讲就是一个超底层的JS库,这个库的作用有两个:一是完成JS脚本的异步加载与执行;二是确保JS脚本按照用户设计的依赖关系加载,即一段脚本执行时,它的所有依赖已经加载完毕了。不同的规范,只是写法用法不同而已,最终目的都是一样的。另外,模块化编程与异步加载不分家,会让JS按需加载,大大提高页面响应速度;模块加载提供命名空间的效果,有效防止作用域污染,并有一定的容错能力。

  AMD规范和CMD规范是目前最为流行的两种模块化编程模式,符合AMD规范的库有RequireJS,符合CMD规范的库有SeaJS。

2. AMD(Asynchronous ModuleDefinition)规范

  AMD翻译过来叫“异步的模块定义”,接口非常简单,只有一个define,完整格式如下:

[javascript] view plain copy
  1. define(‘module-name’, [‘lib1’, ‘lib2’], function (lib1, lib2) {  
  2.          // todo  
  3.          return {};  
  4. });  

第一个参数是模块的名称,可以省略;第二个是模块的依赖,也就是其他模块,如果省略,则此模块没有任何依赖;第三个参数是依赖加载完毕后的回调函数,回调函数的形参是依赖模块的输出,顺序与第二个数组参数一致,回调函数的返回值就是此模块的输出,可以作为其他模块的依赖形参。

2.1  纯数据模块

  这种模块没有任何依赖,不做任何操作,只是为了提供数据源,可以这样定义:

[javascript] view plain copy
  1. define({  
  2.     color: "black",  
  3.     size: "unisize"  
  4. });  

2.2无依赖模块

  如果模块没有任何依赖,但进行了一些预处理,可以这样定义:

[javascript] view plain copy
  1. define(function() {  
  2.     // TODO  
  3.      return {  
  4.         color: "black",  
  5.         size: "unisize"  
  6.     }  
  7. });  

2.3将模块定义为函数

  这种模块是最常见的,意为模块的输出是一个函数,模块的依赖是这个输出函数使用的外部变量,因此这种模块的输出是一个闭包。使用时也要格外小心,尤其是输出函数当做构造函数使用时。

[javascript] view plain copy
  1. define(["my/cart""my/inventory"],  
  2.     function(cart, inventory){  
  3.         //return a function todefine "foo/title".  
  4.         //It gets or sets thewindow title.  
  5.         return function(title){  
  6.             return title ? (window.title= title) :  
  7.                   inventory.storeName + ' ' + cart.name;  
  8.         }  
  9.     }  
  10. );  

2.4引入符合Common规范的模块

[javascript] view plain copy
  1. define(function(require){  
  2. var mod = require("./relative/name");  
  3. return mod;  
  4. });  

2.5 JSONP模块

  JSONP可以跨域,应用非常广泛,使用JSONP模块时,callback必须设成’define’。

[javascript] view plain copy
  1. require(["http://example.com/api/data.json?callback=define"],  
  2.     function (data) {  
  3.         //The data object will be the APIresponse for the  
  4.         //JSONP data call.  
  5.         console.log(data);  
  6.     }  
  7. );  

这种方法一般用于初始化,但错误处理比较复杂。

3. CMD规范

  CMD(Common ModuleDefinition)规范,跟AMD相比,CMD更像CommonJS。至于CMD和AMD哪个好哪个坏,完全没有定论的,主要看个人喜好。CMD的主接口也叫define,其参数可以是string、object、function。define的用法也可以像AMD一样,带id、依赖、回调,但这样就不属于CMD规范了。基本格式如下:

[javascript] view plain copy
  1. define(function (require, exports, module) {  
  2.          // todo  
  3. });  

3.1 require参数

  第一个形参require是一个方法,用来加载外部模块,用法跟2.4类似:

[javascript] view plain copy
  1. define(function (require, exports) {  
  2.     // 获取模块 a 的接口  
  3.     var a = require('./a');  
  4.     // 调用模块 a 的方法  
  5.     a.doSomething();  
  6. });  

  但是这个require是同步模式加载,即如果模块没加载完毕,require后面的语句是不会执行的。AMD规范中都是异步加载,在回调function中使用require,其实相当于告诉构建器要在依赖队列中加入相关模块。

  require同样有异步加载功能,异步加载后,执行响应的回调:

[javascript] view plain copy
  1. define(function(require, exports, module) {  
  2.   // 异步加载一个模块,在加载完成时,执行回调  
  3.   require.async('./b',function(b) {  
  4.     b.doSomething();  
  5.   });  
  6.   // 异步加载多个模块,在加载完成时,执行回调  
  7.   require.async(['./c','./d'], function(c, d) {  
  8.     c.doSomething();  
  9.     d.doSomething();  
  10.   });  
  11. });  

3.2 exports参数

  exports是一个对象,是模块的对外接口,即AMD规范中的return部分,当然,也可以像AMD规范一样,利用return返回接口。两种方式都可以,非常灵活。

[javascript] view plain copy
  1. define(function(require, exports) {  
  2.   // 对外提供 foo 属性  
  3.   exports.foo = 'bar';  
  4.   // 对外提供 doSomething 方法  
  5.   exports.doSomething =function() {};  
  6. });  
  7. define(function(require) {  
  8.   // 通过 return 直接提供接口  
  9.   return {  
  10.     foo: 'bar',  
  11.     doSomething: function() {}  
  12.   };  
  13. });  

值得注意的是,exports形参是不能重新定义的,因为重新定义后,原引用就断掉了,下面的做法起不到输出模块的作用:

[javascript] view plain copy
  1. define(function(require, exports) {  
  2.   // 错误用法!!!  
  3.  exports = {  
  4.    foo: 'bar',  
  5.    doSomething: function() {}  
  6.   };  
  7. });  

3.3 module参数

  module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

  module.id为模块标识,module.uri为模块绝对路径,module.dependencies为模块的依赖数组,module.exports为模块的输出引用。也就是说,3.2节最后的例子中,如果这样写,就是可以工作的:

[javascript] view plain copy
  1. define(function(require, exports, module) {  
  2.   // 正确写法  
  3.   module.exports = {  
  4.     foo: 'bar',  
  5.     doSomething: function() {}  
  6.   };  
  7. });  

同时,对module.exports的赋值必须是同步的,放在settimeout里面是不行的。

4.总结

  可以看出,模块化编程为我们提供了良好的环境,在这个环境中,可以进行更明确的分工合作,让JS开发更专业化。同时,AMD规范和CMD规范都提供了异步加载,让Web前端应用更加高效,用户体验更好。

  至于这两种规范哪个好一些,这个完全看个人编码习惯了,适合你的,就是好的。不过总体来看,CMD有相当一部分兼容了AMD,其最大的区别还是对依赖的引用上。

  AMD主要通过回调函数的形参对依赖进行引用,有多少个依赖,就有多少个形参,当然如果依赖很多也可以不写那么多形参,通过arguments引用,或者在回调函数内部require也是一样的。这里需要说明的是,AMD规范中,尤其是RequireJS中,如果通过var a = require(‘module-name’);这种形式引入依赖,那么module-name必须要在依赖数组中存在,否则会报错。

  CMD规范的依赖引用主要是在回调函数中直接require了,并且这种require是同步的,也提供了异步接口,这样做,势必要影响运行期性能。但是想AMD那种预加载依赖的方式,势必要影响加载时间。这两种影响哪个能容忍,哪个不能容忍,应该具体情况具体分析,视应用而定。

5.参考

CMD规范定义:

https://github.com/seajs/seajs/issues/242

JavaScript的AMD:

http://www.cnblogs.com/happyPawpaw/archive/2012/05/31/2528864.html

RequireJS中文网:

http://www.requirejs.cn/

Sea.js文档:

http://seajs.org/docs/#docs

0 0
原创粉丝点击