YUI3: Creating a Module with Dependencies

前文类似,本文为译文,相关事宜同前文。

[问题]
你想要创建一个自定义的YUI模块并确保它会把另外一个YUI模块作为依赖加载。

[解决办法]
使用YUI.add()方法注册你的code作为一个YUI全局对象(YUI global object)的一个模块,注册的过程中把模块的依赖作为配置对象传给它。在模块名字和定义之后,YUI.add()还接收两个可选的参数:

  • 一个模块的字符串形式的版本数字。这是你的模块的版本号,并不是你的模块所兼容的YUI版本。
  • 一个包含模块metadata的配置对象。目前这个配置对象(configuration object)里面最常见的field是一个叫requires的array,它列出了该模块的所有依赖。对里面所列出的每个模块,YUI会自动在需要的时候加载它们,必要的时候会远程的加载。

下面的例子对前文例子做了修改。Y.Hello.sayHello()不在返回一个字符串值,而是改变一个单个Y.Node的内容。这个hello模块定义了一个node-base的依赖,来确保调用hello模块的时候node.setHTML()是存在并可用的。

为了让事情变得更有趣一些,sayHello()使用了一个叫setNodeMessage()的私有helper函数。因为setNodeMessage()并没有被attach到Y实例上,所以我们并不能直接调用setNodeMessage()。setNodeMessage()使用了Y.one()方法确保input参数是一个YUI的node,然后给该node设置相应的message文本。

<!DOCTYPE html>
<title>Creating a module that depends on a YUI node</title>
<div id="demo"></div>
<script src="http://yui.yahooapis.com/3.8.0/build/yui/yui-min.js"></script> <script>
  YUI.add('hello', function (Y) {

    function setNodeMessage(node, html) {
      node = Y.one(node);
      if (node) {
        node.setHTML(html); }
      }

    Y.namespace('Hello').sayHello = function (node) {
      setNodeMessage(node, 'GREETINGS PROGRAMS');
    };
  }, '0.0.1', {requires: ['node-base']});
  YUI().use('hello', function (Y) {
    Y.Hello.sayHello(Y.one('#demo'));
  });
</script>

不同于前文例子,上面例子的use()并不需要显式的引用node-base。这个新的改进过的hello模块会自动的加载这个依赖。

[讨论]
上面例子中hello模块在requires数组里面列出了依赖的node-base模块。这样就能保证YUI().use()在加载完node-base模块(或者其它任何在requires数组里面列出的依赖模块)之后再加载并attach hello模块到Y上。

在提供依赖的时候,要小心的避免循环依赖。比方说,如果hello模块定义了goodbye模块必须在hello模块之前load,但是goodbye模块却定义hello模块需在goodbye模块之前load,这就有了问题。加载器(The Loader)确实有相关代码来处理metadata里面循环依赖,即便如此,你也不能指望你的code会执行的如你所愿。

如Recipe 1.10里面所描述的(稍后奉上),为了效率考虑,你应该同时在Loader的metadata里面提供你模块的依赖信息。

正如之前所提到的,requires是最重要的field。YUI.add()的其它一些field还包括:

optional
一个在YUI config选项loadOptional被设置成true的时候,引用你的模块时需要自动被加载的模块的名字的数组。比如,autocomplete-base模块定义了一个非必须的对于autocomplete-sources的依赖,该模块里面包括了额外的从YQL和其它remote sources给AutoComplete控件填值的功能。loadOptional默认是false。即使loadOptional被设置成false,如果该非必须的模块代码恰巧已经在页面上被加载过了,该模块依然可以被激活(activate)。当前页面已经存在该模块代码可能是一个较早的YUI().use()调用引用了该模块,也可能是该模块代码被静态的加载了,我们会在Recipe 1.20里面讨论它。

skinnable
这是一个指定你定义的模块是否支持CSS皮肤的Boolean值。如果该值是true,YUI会在document里面自动的创建了一个link element并以如下的URL加载CSS文件:

base/module-name/assets/skins/skin-name/module-name.css

这里面base是config对象的base field的值(在Recipe 1.11里面讨论,稍后奉上),skin-name是皮肤的名字,默认是sam。

use
Deprecated. 一个用来定义“physical rollups”(老的已经deprecated的rollup)的模块名字的数组。

除了模块依赖,后面相关例子(见后文,稍后)里面展示了一个模块里面的私有函数。因为Javascript缺失一个显式声明private的关键字,很多Javascript开发者用一个下划线前缀来象征性的描述私有数据,这会警告其他开发人员这个函数或者变量“应该”是私有的。很多情况下,这种形式已经足够了。

不过,上面例子的setNodeMessage()函数是真正的私有的。一旦YUI执行add()的回调函数,模块用户可以调用sayHello()方法,但是他们却不能直接调用setNodeMessage(),虽然sayHello()内部维护着其对于setNodeMessage()的引用。JavaScript里面一个inner函数可以访问其outer函数的所有member,即使outer函数已经执行完毕。这个重要的语言特性叫做闭包(closure)。

[相关]
Recipe 7.10; Douglas Crockford的“Private Members in JavaScript”.

历史上的今天:

Related posts:

Got something to say? Go for it!

使用新浪微博登陆