兼容前后端共用模块代码(摘自《深入浅出Node.js》)
模块的侧重点
前后端JavaScript分别搁置在HTTP的两端,它们扮演的角色并不同。浏览器端的JavaScript需要经历从同一个服务器端分发到多个客户端执行,而服务器端JavaScript则是相同的代码需要多次执行。前者的瓶颈在于带宽,后者的瓶颈则在于CPU和内存等资源。前者需要通过网络加载代码,后者从磁盘中加载,两者的加载速度不在一个数量级上。
纵观Node的模块引入过程,几乎全都是同步的。尽管与Node强调异步的行为有些相反,但它是合理的。但是如果前端模块也采用同步的方式来引入,那将会在用户体验上造成很大的问题。UI在初始化过程中需要花费很多时间来等待脚本加载完成。
鉴于网络的原因,CommonJS为后端JavaScript制定的规范并不完全适合前端的应用场景。经过一段争执之后,AMD规范最终在前端应用场景中胜出。它的全称是Asynchronous Module Definition,即是“异步模块定义”,详见https://github.com/amdjs/amdjs-api/wiki/AMD。除此之外,还有玉伯定义的CMD规范。
AMD规范
AMD规范是CommonJS模块规范的一个延伸,它的模块定义如下:
1 | define(id?, dependencies?, factory); |
它的模块id
和依赖
是可选的,与Node模块相似的地方在于factory
的内容就是实际代码的内容。下面的代码定义了一个简单的模块:
1 | define(function() { |
不同之处在于AMD模块需要用define
来明确定义一个模块,而在Node实现中是隐式包装的,它们的目的是进行作用域隔离,仅在需要的时候被引入,避免掉过去那种通过全局变量或者全局命名空间的方式,以免变量污染和不小心被修改。另一个区别则是内容需要通过返回的方式实现导出。
CMD规范
CMD规范由国内的玉伯提出,与AMD规范的主要区别在于定义模块和依赖引入的部分。AMD需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块内容中:
1 | define(['dep1', 'dep2'], function (dep1, dep2) { |
与AMD模块规范相比,CMD模块更接近于Node对CommonJS规范的定义:
1 | define(factory); |
在依赖部分,CMD支持动态引入,示例如下:
1 | define(function(require, exports, module) { |
require、exports
和module
通过形参传递给模块,在需要依赖模块时,随时调用require()
引入即可。
兼容多种模块规范
为了让同一个模块可以运行在前后端,在写作过程中需要考虑兼容前端也实现了模块规范的环境。为了保持前后端的一致性,类库开发者需要将类库代码包装在一个闭包内。以下代码演示如何将hello()
方法定义到不同的运行环境中,它能够兼容Node
、AMD
、CMD
以及常见的浏览器环境中:
1 | ;(function (name, definition) { |
摘自《深入浅出Node.js》: 2.7 前后端共用模块