js模块化开发的价值
通过 exports暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。
通过 require引入依赖。这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。
除了解决命名冲突和依赖管理,使用 Sea.js 进行模块化开发还可以带来很多好处:
模块的版本管理。通过别名等配置,配合构建工具,可以比较轻松地实现模块的版本管理。
提高可维护性。模块化可以让每个文件的职责单一,非常有利于代码的维护。Sea.js 还提供了 nocache、debug 等插件,拥有在线调试等功能,能比较明显地提升效率。
前端性能优化。Sea.js 通过异步加载模块,这对页面性能非常有益。Sea.js 还提供了 combo、flush 等插件,配合服务端,可以很好地对页面性能进行调优。
跨环境共享模块。CMD 模块定义规范与 Node.js 的模块规范非常相近。通过 Sea.js 的 Node.js 版本,可以很方便实现模块的跨服务器和浏览器共享。
模块化开发并不是新鲜事物,但在 Web 领域,前端开发是新生岗位,一直处于比较原始的刀耕火种时代。直到最近两三年,随着 Dojo、YUI3、Node.js 等社区的推广和流行,前端的模块化开发理念才逐步深入人心。
前端的模块化构建可分为两大类。一类是以 Dojo、YUI3、国内的 KISSY 等类库为代表的大教堂模式。在大教堂模式下,所有组件都是颗粒化、模块化的,各组件之间层层分级、环环相扣。另一类是以 jQuery、RequireJS、国内的 Sea.js、OzJS 等类库为基础的集市模式。在集市模式下,所有组件彼此独立、职责单一,各组件通过组合松耦合在一起,协同完成开发。
这两类模块化构建方式各有应用场景。从长远来看,小而美更具备宽容性和竞争力,更能形成有活力的生态圈。
总之,模块化能给前端开发带来很多好处。如果你还没有尝试,不妨从试用 Sea.js 开始。
requireJS实现原理
- 我们在使用requireJS时,都会把所有的js交给requireJS来管理,也就是我们的页面上只引入一个require.js,把data-main指向我们的main.js。
- 通过我们在main.js里面定义的require方法或者define方法,requireJS会把这些依赖和回调方法都用一个数据结构保存起来。
- 当页面加载时,requireJS会根据这些依赖预先把需要的js通过document.createElement的方法引入到dom中,这样,被引入dom中的script便会运行。
- 由于我们依赖的js也是要按照requireJS的规范来写的,所以他们也会有define或者require方法,同样类似第二步这样循环向上查找依赖,同样会把他们村起来。
- 当我们的js里需要用到依赖所返回的结果时(通常是一个key value类型的object),requireJS便会把之前那个保存回调方法的数据结构里面的方法拿出来并且运行,然后把结果给需要依赖的方法。
以上就是一个简单的流程。
👍 SeaJS与RequireJS最大的区别
SeaJS对模块的态度是懒执行, 而RequireJS对模块的态度是预执行
如下模块通过SeaJS/RequireJS来加载, 执行结果会是怎样?
define(function(require, exports, module) {
console.log('require module: main');
var mod1 = require('./mod1');
mod1.hello();
var mod2 = require('./mod2');
mod2.hello();
return {
hello: function() {
console.log('hello main');
}
};
});
先试试SeaJS的执行结果
require module: main require module: mod1 hello mod1 require module: mod2 hello mod2 hello main
再来是RequireJS的执行结果
require module: mod1 require module: mod2 require module: main hello mod1 hello mod2 hello main
RequireJS你坑的我一滚啊, 这也就是为什么我不喜欢RequireJS的原因, 坑隐藏得太深了.终于明白玉伯说的那句: "RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug"是什么意思了因此我们得出结论(分别使用SeaJS 2.0.0和RequireJS 2.1.6进行测试)-------------------------SeaJS只会在真正需要使用(依赖)模块时才执行该模块SeaJS是异步加载模块的没错, 但执行模块的顺序也是严格按照模块在代码中出现(require)的顺序, 这样才更符合逻辑吧! 你说呢, RequireJS?
而RequireJS会先尽早地执行(依赖)模块, 相当于所有的require都被提前了, 而且模块执行的顺序也不一定100%就是先mod1再mod2因此你看到执行顺序和你预想的完全不一样! 颤抖吧~ RequireJS!
注意我这里说的是执行(真正运行define中的代码)模块, 而非加载(load文件)模块.模块的加载都是并行的, 没有区别, 区别在于执行模块的时机, 或者说是解析.
AMD 的 CommonJS wrapping
个人觉得,AMD 的这个特性有好有坏:
首先,尽早执行依赖可以尽早发现错误。上面的代码中,假如 a 模块中抛异常,那么 main.js 在调用 factory 方法之前一定会收到错误,factory 不会执行;如果按需执行依赖,结果是:1)没有进入使用 a 模块的分支时,不会发生错误;2)出错时,main.js 的 factory 方法很可能执行了一半。
另外,尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。例如模块 a 依赖了另外一个需要异步加载数据的模块 b,尽早执行 b 可以让等待时间更短,同时如果 b 最后没被用到,带宽和内存开销就浪费了;这种场景下,按需执行依赖可以避免浪费,但是带来更长的等待时间。
我个人更倾向于 AMD 这种做法。举一个不太恰当的例子:Chrome 和 Firefox 为了更好的体验,对于某些类型的文件,点击下载地址后会询问是否保存,这时候实际上已经开始了下载。有时候等了很久才点确认,会开心地发现文件已经下好;如果点取消,浏览器会取消下载,已下载的部分就浪费了。
shim
这是因为 main.js 中 mod1 和 mod2 两个模块并行加载,且加载完就执行,所以前两行输出顺序取决于哪个 js 先加载完。如果一定要让 mod2 在 mod1 之后执行,需要在 define 模块时申明依赖,或者通过 require.config 配置依赖:
JSrequire.config({ shim: { 'mod2': { deps : ['mod1'] } }});