require.js读书笔记 2.usage

USAGE§ 1

Load JavaScript Files 加载javascript文件夹§ 1.1

requireJs用一种新的script加载方法,这种方法和传统<script>标签是完全不同的。它可以运行地更快,并且进行更好地优化,它的主要目的就是为了支持(encourage)模块化(modular)代码的加载。作为其中的一部分,它支持利用模块ID来加载script,而不是script标签里的url属性。

requireJs加载的所有代码地址都是相对于baseUrl的。页面顶层script标签有一个特殊的属性data-main,require.js用它来启动脚本加载页面,而baseUrl通常设置成这个标签所在的文件夹里。data-main attribute是一个特殊的属性,require.js会用这个属性进行加载。下面这个例子会展示了baseUrl的设置:

<!--This sets the baseUrl to the "scripts" directory, and
    loads a script that will have a module ID of 'main'-->
<script data-main="scripts/main.js" src="scripts/require.js"></script>

或者,baseUrl可以通过RequireJS config手动(manually)地设置。如果没有明确的(explicit)config设置,或者没有使用data-main属性,那么默认的baseUrl就是包含requireJs的HTML页面所在的目录。

requireJs默认所有依赖(dependence)资源都死scripts,所以写模块ID时不需要.js的后缀。requireJs翻译模块ID的路径时会自动加上.js尾缀的。运用paths config标签,你可以设置一组scripts脚本的位置。相对于<script>标签,这些能让你用更少的字符来加载script。

有时候你想直接饮用一个script,而不是依照(conform)“baseUrl+paths"规则来找它。如果一个模块ID由以下之一的规则,这个ID就不会通过”baseUrl+paths"配置来加载script,而是像普通的script url属性来加载。

  • 以.js结束
  • 以“/”开始
  • url协议(protocol),像 "http:" or "https:".

通常地说,最好通过baseUrl和paths来设置模块ID的路径。这样所,你可以很方便地重命名和重定位脚本(configuring the paths to different locations)。

相似的,为了避免配置凌乱,最好避免多级嵌套(deep folder hierarchies)的方式来加载代码。要么将所有的scripts放在baseUrl的目录中,不然将你的代码分置为目录(library)/第三方目录库(vendor)的结构,可以像以下所示:

  • www/
    • index.html
    • js/
      • app/
        • sub.js
      • lib/
        • jquery.js
        • canvas.js
      • app.js

in index.html:

<script data-main="js/app.js" src="js/require.js"></script>

and in app.js:

requirejs.config({
    //设置默认模块ID的路径 js/lib
    baseUrl: 'js/lib',
    //另外,如果模块ID以app开始,
    //它会从js/app目录加载。paths设置时相对于baseUrl的,绝不会包括“.js”的,但是paths设置可以是相对directory的
    paths: {
        app: '../app'
    }
});

开始main app的逻辑。
requirejs(['jquery', 'canvas', 'app/sub'], function ($, canvas, sub) { //jQuery, canvas and the app/sub module are all //loaded and can be used here now. });

在这个例子中,第三方库(vendor其实是供应商的意思)如jQuery,并没有将它的版本号显示在文件名中。如果你想跟踪版本号,建议新开一个单独的文件来记录,或者你可以用一些工具,像volo,可以将package.json打上版本信息,但文件名还是jQuery.js。这有助于你的配置最小化,避免为每个版本的库设置paths路径。例如,将"jquery"配置成(configure)“jquery-1,7,2"

理想状态下(ideally),每个加载的脚本都是通过define()来定义的一个模块。然而,有些"浏览器全局变量注入"型传统/遗留(legacy)浏览器可能不能用define()来定义它们的依赖关系。为此(for those),你可以用shim config来解析它们的依赖关系。

如果你不想解析它们的依赖关系,你可能会得到一些加载错误,基于速度的原因(for speed),requireJs会异步( asynchronously)、无序(out of order)地加载脚本。

 

data-main Entry Point§ 1.2

data-main属性是一个特殊属性,require.js在加载脚本的时候会检查(check)它:

<!--当require.js加载的时候,它会忽视script/main.js的其他script标签属性-->
<script data-main="scripts/main" src="scripts/require.js"></script>

你可以在data-main中设置配置选项,然后加载你的第一个应用模块(application module)。注意:require.js的标签加载的模块是异步的async attribute。这意味着,如果你在这个页面加载了其他scripts,则不能保证通过require.js加载的页面可以先于这些脚本加载完毕。

举个例子,以下的构造不能保证foo模块的require.config的路径设置会先于require()foo模块执行:

<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>
// contents of main.js:
require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});
// contents of other.js:

//这段代码可能会在main.js 的require.config()之前执行。如果这放生了,require.js会加载'scripts/foo.js‘额不是'scripts/libs/foo-1.1.3.js

require(['foo'], function(foo) { });

如果你想在HTML页面中调用require(),最好不要用data-main。data-main只用在页面只需要一个入口的时候。如果页面想在行内调用require(),最好如下所示书写代码

<script src="scripts/require.js"></script>
<script>
require(['scripts/config']), function() {
    // Configuration loaded now, safe to do other require calls
    // that depend on that config.
    require(['foo'], function(foo) {

    });
});
</script>

Define a Module§ 1.3

一个模块不同于传统脚本文件的地方是,它定义了一个模块范围来避免污染全局环境。它明确地列举了依赖的文件,并以函数(定义那个模块的函数)参数的形式将这些依赖注入。RequireJs的模块是Module Pattern的一个扩展,这样的好处是不需要全局地引入其他模块。

RequireJs的模块属性让它们可以尽快地被加载,就算加载是无序的,依赖也会按照争取的顺序。因为没有创建全局变量,所以在一个页面中可以创建多个版本的模块load multiple versions of a module in a page.

(如果你熟悉或者使用过commonjs,那么请看 CommonJS Notes来了解requireJs和CommonJs的映射(map to)关系)

一个磁盘文件应该只定义一个模块。optimization tool工具可以将模块分组优化(grouped into optimized bundles).

Simple Name/Value Pairs§ 1.3.1

如果一个模块没有任何依赖,仅含任何值/对(name/value),则在define()中定义这些值/对就好了。

//Inside file my/shirt.js:
define({
    color: "black",
    size: "unisize"
});

Definition Functions§ 1.3.2

如果以个模块没有依赖,但是需要一些函数来做一些setup的工作,那就在define中定义该函数:

//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
    //Do setup work here

    return {
        color: "black",
        size: "unisize"
    }
});

Definition Functions with Dependencies§ 1.3.3

如果一个模块有依赖,第一个参数(arguments)应该是一串依赖名的数组,第二个参数应该是定义的函数。一旦所有的依赖加载完毕,这个函数就会被调用来定义该模块。定义这个模块的函数应该返回一个对象。依赖会以一个参数的形式传给函数,参数列表和依赖名称列表一一对应

//my/shirt.js 有一些依赖, cart和inventory,都和shirt.js在同一个目录下
define(["./cart", "./inventory"], function(cart, inventory) {
        //return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);
  • my/cart.js
  • my/inventory.js
  • my/shirt.js

这个函数有两个参数“cart”和“inventory”。对应的模块以模块名"./cart" 和"./inventory"展示。

这个函数会在my/cart ,my/inventory被加载后调用,并且获得cart和inventory的函数参数。

严重不允许模块定义全局的变量,这样,多版本的模块才能存在于同一个页面。另外,函数的参数顺序应该和依赖模块的顺序一样。

返回的对象定义了"my/shirt"模块。这样定义模块,"my/shirt"就不会以全局对象的方式存在。

 

Define a Module as a Function§ 1.3.4

模块的并不一定要有返回值。任何函数的返回值(return value)都是允许的。此处有一个模块如它所定义地返回了一个函数:

/A module definition inside foo/title.js. It uses
//my/cart and my/inventory modules from before,
//but since foo/title.js is in a different directory than
//the "my" modules, it uses the "my" in the module dependency
//name to find them. The "my" part of the name can be mapped
//to any directory, but by default, it is assumed to be a
//sibling to the "foo" directory.
define(["my/cart", "my/inventory"],
    function(cart, inventory) {
        //return a function to define "foo/title".
        //It gets or sets the window title.
        return function(title) {
            return title ? (window.title = title) :
                   inventory.storeName + ' ' + cart.name;
        }
    }
);

 

 

Define a Module with Simplified CommonJS Wrapper§ 1.3.5

如果你想重新用一些以CommonJS module format的方式写的代码,而这些代码难以用上述的依赖数组的方式来写,你可以考虑直接将这些依赖对应到本地变量。你可以使用一个简单的commonJs包装来实现simplified CommonJS wrapper

define(function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        //Return the module value
        return function () {};
    }
);

这个包装依赖于Function.prototype.toString()将函数内容赋予有意义的字符串。这PS3或者一些老的Opera手机浏览器上表现不好。用optimizer将这些依赖变成一个数组的格式,以便与在这些设备上用。
更多的信息参考 CommonJS page,  "Sugar" section in the Why AMD page.

 

Define a Module with a Name§ 1.3.6

你可能会看到一个define()中包含了模块名作为第一个参数:

    //Explicitly defines the "foo/title" module:
    define("foo/title",
        ["my/cart", "my/inventory"],
        function(cart, inventory) {
            //Define foo/title object in here.
       }
    );

这些通常是optimization tool生成的。你可以自己命名模块名,但这会使模块更难移植——如果你将文件夹移到另一个目录中,你还得改它们的名字。最好避免对模块硬编码,而让optimization tool来生成模块名。这个工具需要生成模块名,以便将这些模块打成一个包(be bundled),使浏览器更快加载。

Other Module Notes§ 1.3.7

One module per file.: 每个js文件只能定义一个模块,这是模块寻找机制的自然要求。多个模块会被 optimization tool打包成一个模块,但你需要用将多个模块放到一个文件夹里.

Relative module names inside define(): 对于define()中("./relative/name")的模块调用,确保将require本身作为一个依赖注入到模块中, 这样,相对路径的名称才能被正确地解析:

define(["require", "./relative/name"], function(require) {
    var mod = require("./relative/name");
});

更好的方式是,像commomjs一样使用一个更短的解析:

define(function(require) {
    var mod = require("./relative/name");
});

这个模块利用Function.prototype.toString()去寻找require()的调用,并把它们加到依赖的数组中。在require()的工作路径下,代码会被正确的解析。

如果你在一个目录中创建了很多模块,相对路径通常是很有用的。你分享这个目录给其他人,你也可以对这个目录中的其他模块做一些改动,而不必知道模块的名称。

Generate URLs relative to module: 你可能需要生成一个相对于模块的URL. 你可以将require作为一个依赖注入,然后用require.toUrl()来生成一个url:

define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});

Console debugging: 如果你想调用一个已经通过 require(["module/name"] , function(){}) 方式加载的模块,你可以用字符串作为模块名的参数来调用它:

require("module/name").callSomeFunction()

注意,这个只对于已经用"module/name"方式异步加载的模块有效。如果用一个相对路径,像'./module/name',那只会在define内部工作有效。

Circular Dependencies§ 1.3.8

如果你定义了一个循环依赖("a" needs "b" and "b" needs "a"), 那么当"b"模块被调用的时候, 它会得到一个未定义的a值。“b”可以在已经被require()定义好后来获取“a”(确保将require作为一个依赖注入):

//Inside b.js:
define(["require", "a"],
    function(require, a) {
        //"a" in this case will be null if "a" also asked for "b",
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

通常,你不需要用require()来获取模块, 只要将依赖的模块作为参数传给函数就可以了.循环依赖是很少出现的,如果出现了,你就想重新考虑设计问题了。然而,有时候也是需要循环依赖的,如果这样的话,可以考虑上述的代码组织。

如果你非常熟悉commonJs,你也可以用exports的来制造一个空模块,这也可以以依赖的形式被其他模块获取。这样做的话,你可以在循环依赖的任意一方安全地使用其他模块。.这只在每个模块都以object作为模块值输出的时候有效,函数形式无效:

//Inside b.js:
define(function(require, exports, module) {
    //If "a" has used exports, then we have a real
    //object reference here. However, we cannot use
    //any of "a"'s properties until after "b" returns a value.
    var a = require("a");

    exports.foo = function () {
        return a.bar();
    };
});

或者,如果你再用数组作为依赖,可以调用特殊的exports模块 'exports' dependency:

//Inside b.js:
define(['a', 'exports'], function(a, exports) {
    //If "a" has used exports, then we have a real
    //object reference here. However, we cannot use
    //any of "a"'s properties until after "b" returns a value.

    exports.foo = function () {
        return a.bar();
    };
});

Specify a JSONP Service Dependency§ 1.3.9

JSONP 是javascript中服务调用的一种方式。它通过script标签发起HTTP GET请求,是实现跨域的一种公认手段。

要在requireJs使用JSONP,要在回调中将参数属性设置为”define“。这意味着你可以将获取到的JSONP URL的值作为一个模块定义。

这里有一个调用JSONP API端点的例子。在这个例子中,JSONP callback参数的值设置为”callback“, 所以"callback=define"告诉API将返回的JSON打包放在define()中:

require(["http://example.com/api/data.json?callback=define"],
    function (data) {
        //The data object will be the API response for the
        //JSONP data call.
        console.log(data);
    }
);

这种JSONP的用法仅限于JSONP服务的初始化initial application setup. 一旦JSONP服务超时,意味着其他通过define()的模块不会执行,所以这种错误处理是不强健的。

Only JSONP return values that are JSON objects are supported.一个JSONP的返回是一个数组,如果是一个字符串或者数字式不会工作的。

这个机制不能被用在long-polling 类的JSONP连接-- 那些用来处理实时流的APIs. 这种类型的api在收到每个返回的时候一般会做script清理,RequireJS只会获取一次JSONP URL—— 后继使用require()或者define()发起的URL会得到一个缓存值。

加载JSONP服务的错误通常以服务超时的形式出现。因为简单加载一个script标签不会得到很多网络错误信息。你可以用override requirejs.onError() 来获取错误。更多信息参见 Handling Errors section.

Undefining a Module§ 1.3.10

有一个全局的函数requirejs.undef(), 用来undefining一个模块.它会重设加载器的内部(internal)状态来消除前一个模块的定义。

然而,如果一个模块已经被定义了,并且在其他模块中作为一个依赖被调用执行了,全局函数是不会清除这个模块的。 所以它仅在无其他模块持有该模块错误的时候有用,或者当未来需要加载这模块时有点用。 See the errback section for an example.

如果想知道更多关于undefined的复杂语法,the semi-private onResourceLoad API may be helpful.

posted @ 2014-10-22 18:10  欧欧欧子  阅读(174)  评论(0)    收藏  举报