SeaJS 和 RequireJS 的异同
相同点是:要解决的问题相同,都是浏览器端的模块化开发,目标一致。
不同点有不少:
1. 遵循的规范不同
RequireJS 遵循的是 Modules/AMD 规范。
SeaJS 遵循的是 Mdoules/Wrappings 规范的 define 形式。
AMD 规范在 CommonJS 社区争议很大,规范里太多 RequireJS 的影子。社区里不少人反感 RequireJS 打着 CommonJS 的口号,甚至建议 AMD 自立门户,比如最近的这篇讨论:Split off AMD?
从这两个规范本身来说,Modules/Wrappings 规范更简洁优雅,不信的话,你读读它们各自的描述就清楚。
2. factory 的执行时机不同
在 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 规范的提前下载和延迟执行,是一种合理的权衡。
3. 设计理念不同
如果你用过 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"]); // 加载多个模块
4. 聚焦点有差异
SeaJS 从一开始,到现在,都是 focus on web, 努力成为浏览器端的模块加载器。sea.js 源码里,只有和浏览器相关的代码,不像 requirejs 一样,牵三挂四,有为了能在 Rhino 和 node 下运行的代码,甚至还有为了与 jQuery 集成的代码。聚焦的好处之一是,能减少文件大小:
SeaJS 目前的大小是:3.2K (gzip)
RequireJS 的大小是:5.4K (gzip)
seajs 的源码,目前是分多个文件开发的,每个文件都很聚焦,有完善的测试用例。requirejs 目前依旧是一个大文件,不利于维护。
5. 最后,还是理念不一样
RequireJS 有一系列插件,功能很强大,但破坏了模块加载器的纯粹性,个人觉得不妥。
SeaJS 则努力保持简单,追求简洁之美。除非必要,勿增实体。在功能上,SeaJS 有个优势是,支持 CSS 模块的加载。RequireJS 因为技术原因,目前版本尚未实现。
最后的最后
根据实际需求选择就好,关键是要养成模块化开发的好习惯。