模块化开发已经是大势所趋,看到玉伯在介绍seajs和requirejs时,说“RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范”。
AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD
CMD 规范在这里:https://github.com/seajs/seajs/issues/242
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。
还有不少⋯⋯
这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。
目前这些规范的实现都能达成浏览器端模块化开发的目的。
区别:
1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。
4. 还有一些细节差异,具体看这个规范的定义就好,就不多说了。
RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。
两者的主要区别如下:
定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
还有不少差异,涉及具体使用方式和源码实现,欢迎有兴趣者研究并发表看法。
总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。
最后,向 RequireJS 致敬!RequireJS 和 Sea.js 是好兄弟,一起努力推广模块化开发思想,这才是最重要的。
--------------------------------------------------------------------------------------------------------------------------
相同点是:要解决的问题相同,都是浏览器端的模块化开发,目标一致。
不同点有不少:
RequireJS 遵循的是 Modules/AMD 规范。
SeaJS 遵循的是 Mdoules/Wrappings 规范的 define 形式。
AMD 规范在 CommonJS 社区争议很大,规范里太多 RequireJS 的影子。社区里不少人反感 RequireJS 打着 CommonJS 的口号,甚至建议 AMD 自立门户,比如最近的这篇讨论:Split off AMD?
从这两个规范本身来说,Modules/Wrappings 规范更简洁优雅,不信的话,你读读它们各自的描述就清楚。
在 RequireJS 里,模块有多种书写格式,推荐的是:
define(["./a", "./b"], function(a, b) {
a.doSomething();
b.doSomething();
});
在 SeaJS 里,模块只有一种书写格式:
define(function(require, exports, module) {
require("./a").doSomething();
require("./b").doSomething();
});
SeaJS 的模块书写格式,RequireJS 也支持。为了便于讨论,我们称呼 define 方法中的 function 参数为模块的 factory. 无论是在 RequireJS 还是 SeaJS 里,在执行 factory 之前,都会确保依赖的模块已经下载好:
/* a.js */
define(factory);
/* b.js */
define(factory);
/* c.js */
define(['./a', './b'], factory);
每个模块都有自己的 factory. 在 AMD 的书写格式下,上面例子中,模块 c 的 factory 在执行时,会接收 a 和 b 两个参数。这意味着,c 依赖的所有模块,都是在一开始就得执行好,即便有可能不需要执行,比如:
define(function(require) {
// BEGIN
if(some_condition) {
require('./a').doSomething();
} else {
require('./b').soSomething();
}
// END
});
在 AMD 规范里,在 BEGIN 处,a 和 b 的 factory 都已经执行好。在 Wrappings 规范里,在 BEGIN 处,a 和 b 的 factory 还没未执行,在 END 处时,根据条件,只会执行其中一个。
可以看出,Wrappings 规范更“懒”,更节省 CPU. 更符合 nodejs 等环境下的使用习惯。
AMD 规范只所以采用提前执行,可以参考作者的说明:Standards and proposals for JavaScript Modules and jQuery, 提到了两点理由,但并不被社区认可。我个人觉得这是一种权衡,但是是一种糟糕的权衡,破坏了与 Modules/1.0 规范的和谐性。有兴趣的可以搜索 Google Groups, 有相当多的讨论。
Wrappings 规范则尽可能的保留了 Modules/1.0 规范中的习惯。权衡点在于,对于条件语句中 require:
if(true) {
require('./a').doSomething();
} else {
require('./b').soSomething();
}
在 node 等环境中,能直接同步读取文件,可以做到只加载和执行模块 a, 不加载模块 b. 但在浏览器端,要实现这点,很困难(可以通过类似 Jscex 的方式来实现,但复杂度急增)。面对现实,目前基本所有浏览器端的 loader, 都是把依赖的模块都下载下来。从打包部署的角度考虑,这样做并不会对性能造成影响,反而能让不同条件分支,共享同一缓存。Wrappings 规范的提前下载和延迟执行,是一种合理的权衡。
如果你用过 RequireJS, 会知道:require 方法极其灵活,比如下面这些:
require('a') -- gets exports of module a
require(['a']) -- fetch module a according to module name scheme
require(['a.js']) -- fetch a.js directly relative to current page
require({...}) -- set loader config
稍微有点变化,做的事情就不一样,个人很不喜欢,社区里也有不少人讨厌这种风格。
在 SeaJS 里,API 的设计理念是:
SeaJS 的公共 API 只有 8 个:速查手册, 职责很清晰,简单明了。
不拘泥规范,大家可以看到,SeaJS 里并没有采用 Modules/Wrappings 规范里的 module.declare. 这是因为:
module.declare(function(require, exports, module) {
module.id // 这是参数 module
});
module.declare(function(require, exports) {
module.id // 这是全局 module
});
显然,module.declare 导致 module 变量和 this 一样,随着环境的变化而变化。对于新手来说,这会带来迷惑。不如直接引入 define 来得简洁明了。随着 RequireJS 的推广,大家对 define 的接受程度也更高,更容易被理解。还有一个好处:使得 SeaJS 的模块,理论上讲,可以直接运行于 RequireJS 上。
适度灵活,举个例子:
seajs.use("./a"); // 加载一个模块
seajs.use(["./a", "./b"]); // 加载多个模块
SeaJS 从一开始,到现在,都是 focus on web, 努力成为浏览器端的模块加载器。sea.js 源码里,只有和浏览器相关的代码,不像 requirejs 一样,牵三挂四,有为了能在 Rhino 和 node 下运行的代码,甚至还有为了与 jQuery 集成的代码。聚焦的好处之一是,能减少文件大小:
SeaJS 目前的大小是:3.2K (gzip)
RequireJS 的大小是:5.4K (gzip)
seajs 的源码,目前是分多个文件开发的,每个文件都很聚焦,有完善的测试用例。requirejs 目前依旧是一个大文件,不利于维护。
RequireJS 有一系列插件,功能很强大,但破坏了模块加载器的纯粹性,个人觉得不妥。
SeaJS 则努力保持简单,追求简洁之美。除非必要,勿增实体。在功能上,SeaJS 有个优势是,支持 CSS 模块的加载。RequireJS 因为技术原因,目前版本尚未实现。
根据实际需求选择就好,关键是要养成模块化开发的好习惯。