YUI Modules 与 AMD/CMD,哪一种方式更好?

YUI Modules 使用了”沙箱 + 命名空间“的方式;AMD 和 CMD 都使用了每个模块提供接口的方式,只不过是代码的执行时间不太相同。 对于 YUI Modules 和 AMD/CMD 的两种策略,孰优孰劣,不太好说。个人认为,从模块独立的角度来看,由于命名空间的存在,YUI Modules 的各个模块除了依赖关系外,还是有一定的耦合的。比如我们还是要小心各个模块之间的命名空间的约定,这就导致模块之间具有一定的复杂度。不知道我理解的对不对? 也…
关注者
259
被浏览
37428

6 个回答

自己又学习整理了下,不一定正确,欢迎大家抛砖。

在 YUI2 的那个时代,我们学到了命名空间(namespace)的概念,同时也遗留了一些问题需要解决。

模块依赖关系
功能越来越复杂,模块文件越来越多,我们需要时刻小心模块之间的依赖。

多版本共存问题
由于遗留系统的缘故,我们有以下两个版本的 hello.js 需要共存:
<script src="path/to/my/hello/1.0/hello.js"></script>
<script src="path/to/my/hello/2.0/hello.js"></script>

这样,除非我们将它们各自的 API 放在不同的命名空间下,否则就会导致命名冲突(当然,你也可以在 2.0 版本的 hello.js 的 API 设计上做一些兼容性设计)。
YUI.hello.older.sayHello();
YUI.hello.newer.sayHello();

所以,YUI3来了,它解决了模块依赖的问题,并一定程度上解决了多版本共存问题。之间我有说过它使用了 Sandbox + namespace 的方式。

关于 Sandbox ,我们先举个栗子:
// path/to/my/hello/1.0/hello.js
YUI.add('hello', function(Y) {

    Y.sayHello = function(msg) {
        Y.DOM.set(el, 'innerHTML', 'Hello!');
    };
}, '1.0', {
    requires: ['dom']
});
// index.html
<div id="entry"></div>
<script>
YUI().use('hello', function(Y) {
    Y.sayHello('entry');   // <div id="entry">Hello!</div>
});
</script>

我们用到了两个接口,其中 YUI.add 看起来和 AMD 的 define 相似,而 YUI().use 和 require() 有个很大的不同,就是我们只有一个参数 Y ,那 Y 是什么呢?

Y是一个独立的构造函数实例,它存在于运行时,它包括了所有依赖的模块的 API。YUI().use 的执行分成两个步骤,首先是加载依赖模块,然后是将它们 attach 到 Y 上。看下面的栗子:
YUI.add('moduleA', function(Y) {

    Y.moduleA = function() {}
});

YUI.add('moduleA', function(Y) {

    Y.moduleB = {
        run: function() {}
        jump: function() {}
    };
});

YUI.add('moduleC', function(Y) {

    Y.play = function() {
        
    }
});


YUI().use(['moduleA', 'moduleB', 'moduleC'], function(Y) {
    console.log(Y.moduleA);
    Y.moduleB.run();
    Y.moduleB.jump();
    Y.play();
});
是不是有点像 $.extend(my, base) 呢?通过 Sandbox ,我们可以支持多版本共存,我们回到之前的 hello 例子,现在我们需要一个新的版本。
// path/to/my/hello/2.0/hello.js
YUI.add('hello', function(Y) {

    Y.sayHello = function(id) {
        var el = Y.DOM.byId(id);
        Y.DOM.set(el, 'innerHTML', 'Hello too!');
    };

}, '2.0', {
    requires: ['dom']
});
// index.html
<div id="entry"></div>
<script>
YUI().use('hello1.0', function(Y) {
    Y.sayHello('entry');   // <div id="entry">Hello!</div>
});
YUI().use('hello2.0', function(Y) {
    Y.sayHello('entry');   // <div id="entry">Hello too!</div>
});
</script>
两个版本的 hello.js 都存在于他们各自的 Sandbox 中,互不打扰。

但是呢, YUI3 并没有真正地实现多版本共存,请看下面的使用情况:
// index.html
<div id="entry"></div>
<script>
YUI().use(['hello1.0', 'hello2.0'], function(Y) {
    Y.sayHello('entry');   // <div id="entry">Hello too!</div>
});
我们又只能回到 YUI2 时代解决多版本共存的问题。

而且,YUI3 将所有的需要的模块 attach 到了同一个运行时构造实例,为了防止模块之间的命名冲突,所以我们仍然需要 namespace 这个函数,比如:
YUI.add('moduleA', function(Y) {
    // Y.run = function() {};
    Y.namespace('moduleA').run = function() {};
});

YUI.add('moduleA', function(Y) {
    // Y.run = function() {};
    Y.namespace('moduleB').run = function() {}
});

YUI.use(['moduleA', 'moduleB'], function(Y) {
    // Y.run = function() {};
    Y.moduleA.run();
    Y.moduleB.run();
});

而 AMD/CMD 就没有这个问题:
define('moduleA', function() {
    return {
        run: function() {}
    };
});

define('moduleB', function() {
    return {
        run: function() {}
    };
});

require(['moduleA', 'moduleB'], function(moduleA, moduleB) {
    moduleA.run();
    moduleB.run();
});


使用 AMD/CMD ,我们可以完全不使用命名空间这个概念,这样在大型的系统中,完全可以避免有深度的命名空间,以减少组织方式上的困难以及查找上的性能消耗。

通过自己的比较,感觉 AMD/CMD 比 YUI Module 要好很多,不知道 YUI Module 的优势是什么?
@欣悦 分析的非常详细,赞一个!

YUI Module 出现非常早,在很多方面确实落后于 AMD、CMD 等后来者。

但,这重要吗?

说到底,所有的模块规范最核心的是定义依赖关系,并提供底层透明的使用接口,方便开发者调用。YUI Module 完全满足这一点。由于需要解决大型网站面临的种种开发维护上的挑战,YUI3 选择了沙箱这种利好协同开发的机制,从而导致不得不沿用 YUI2 时代命名空间。或许,这也是一种传承吧。

其实避免命名空间冲突的方式很简单,将模块名和定义的命名空间一一对应即可。不过这样仍然会比 AMD、CMD 的要长,这一点必须承认。

最后,YUI3 真正的精华在于强大、健壮、自成体系的组件框架,至于 Loader 和命名空间上的一些短板,我觉得大可不必过于纠结。
为什么?