45

假设这是我的 config.js 或 main.js:

require.config({
    // paths are analogous to old-school <script> tags, in order to reference js scripts
    paths: {
        jquery: "libs/jquery-1.7.2.min",
        underscore: "libs/underscore-min",
        backbone: "libs/backbone-min",
        jquerymobile: "libs/jquery.mobile-1.1.0.min",
        jquerymobilerouter: "libs/jquery.mobile.router.min"
    },
    // configure dependencies and export value aliases for old-school js scripts
    shim: {
        jquery: ["require"],
        underscore: {
            deps: ["jquery"],
            exports: "_"
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        },
        jquerymobilerouter: ["jquery", "backbone", "underscore"],
        jquerymobile: ["jquery", "jquerymobilerouter", "backbone", "underscore"]
    }
});
require(["jquery", "backbone", "underscore", "app/app.min", "jquerymobilerouter", "jquerymobile"], function ($, Backbone, _, App) {
    console.log($);
    console.log(Backbone);
    console.log(_);
    $("body").fadeIn(function () {
        App.init();
    });
});
  1. 如果我理解正确,paths配置选项允许您引用脚本,即<script>HTML 中的标记。假设是这种情况,我是否仍然需要在下面的实际 require 语句中为 jQuery 之类的脚本加上 a$或下划线 a的别名?_我不得不这样做似乎很奇怪,因为如果您使用标准<script>标签引用 jQuery,$则可以在整个脚本中自动使用。使用 不应该是一样的paths吗?

  2. 我是shimconfig 选项的新手,我知道它已经取代了不推荐使用的order!插件。exports该物业实际上是做什么的?它似乎没有为脚本创建别名;例如,如果我将exportsfor 下划线设置为"whatever",然后尝试设置为console.log(whatever),则它是未定义的。那么有什么意义呢?

  3. 如何“全局”正确使用 jQuery 之类的脚本?也就是说,能够$在我的 App.js 模块或我的“app”文件夹中的任何其他模块中使用别名的正确方法是什么?我每次都必须在每个单独的模块和别名中都需要 jQuery$吗?或者我在这里做的方式是否正确?

我也非常感谢对这个特定脚本的任何其他批评;在我看来,Require.js 的文档还有很多不足之处;我真正想了解更多的事情似乎被掩盖了,让我摸不着头脑。

4

2 回答 2

23

只是为了澄清关于 的任何混淆exports,假设任何 shim 库都将属性附加到全局上下文(windowroot),或修改已经存在的全局属性(例如 jQuery 插件)。当 requireJS 获取加载填充依赖项的命令时,它会检查与exports该填充配置的值匹配的属性的全局上下文,如果找到它,则将其作为该模块的值返回。如果它没有找到它,那么它会加载相关的脚本,等待它执行,然后找到全局符号并返回它。

要记住的一个重要事实是,除非 shim 配置包含一个exports值,init否则不会执行该配置上的任何方法。依赖加载器必须在初始化该模块之前找到该模块的值(这是exports指定的),这就是如果init该模块有填充程序则需要该属性的原因。

更新:我还需要指出,如果有问题的模块在define任何地方调用,您为该模块拥有的任何 shim 配置都将被忽略。这实际上让我有些头疼,因为我想使用 shim 配置来调用 jQuery 的jQuery.noConflict(true)方法来取消全局化 jQuery 并将其范围限定为需要它的模块,但无法让它工作。(有关如何使用 map config 而不是 shim config 轻松执行此操作的信息,请参阅底部的更新。)

更新 2:requireJS 谷歌组最近的一个问题让我意识到我的解释可能有点误导,所以我想澄清一下。RequireJS 只会在至少一次通过 requireJS 加载的情况下重新使用已填充的依赖项。也就是说,如果您只是<script>在托管页面上有一个标签(例如,下划线),就像这样:

<script src='lib/underscore.js'></script>
<script src='lib/require.js' data-main='main.js'></script>

...并且您的 requireJS 配置中有类似的内容:

paths: {
    'underscore': 'lib/underscore'
},
shim: {
    'underscore': {
        exports: '_'
    }
}

那么第一次执行define(['underscore'], function (_) {});orvar _ = require('underscore');时,RequireJS 将重新加载下划线库,而不是重新使用之前定义的window._,因为据 requireJS 所知,您之前从未加载过下划线。当然,它可以检查是否_已在根范围上定义,但它无法验证_已经存在的与paths配置中定义的相同。例如,默认情况下两者都prototype分配jquery给自己window.$,如果 requireJS 假定 'window.$' 是 jQuery,而实际上它是原型,那么你将处于糟糕的境地。

所有这一切都意味着,如果你混合搭配这样的脚本加载样式,你的页面最终会变成这样:

 <script src='lib/underscore.js'></script>
 <script src='lib/require.js' data-main='main.js'></script>
 <script src='lib/underscore.js'></script>

第二个下划线实例是由 requireJS 加载的实例。

基本上,必须通过 requireJS 加载一个库,以便 requireJS 了解它。然而,一次你需要下划线时,requireJS 会说“嘿,我已经加载了它,所以只要交回任何exports值,不用担心加载另一个脚本。”

这意味着您有两个真正的选择。一种是我认为的反模式:根本不使用 requireJS 来表达全局脚本的依赖关系。也就是说,只要库将全局附加到根上下文,您就可以访问它,如果没有明确要求该依赖关系。您可以看到为什么这是一种反模式——您基本上只是消除了使用 AMD 加载程序的大部分优势(显式依赖项列表和可移植性)。

另一个更好的选择是使用 requireJS 加载所有内容,以至于您应该自己创建的唯一实际脚本标签是最初加载 requireJS 的标签。您可以使用 shims,但 95% 的情况下,将 AMD 包装器添加到脚本中并不难。将所有非 AMD 库转换为与 AMD 兼容可能需要更多的工作,但是一旦您完成一两个,它就会变得容易得多 - 我可以使用任何通用 jQuery 插件并将其转换为 AMD 模块不到一分钟。通常只是添加的问题

define(['jquery'], function (jQuery) {

在顶部,并且

    return jQuery;
});

在底部。我将'jquery'映射到jQuery而不是$我注意到这些天大多数插件都包装在这样的闭包中的原因是:

(function ($) {
    // plugin code here
})(jQuery);

注意预期的范围是个好主意。您当然可以$直接将“jquery”映射到,假设插件不希望找到jQuery而不是$. 这只是基本的 AMD 包装器 - 更复杂的包装器通常会尝试检测正在使用的加载器类型(commonJS 与 AMD 与常规 ol' 全局变量)并根据结果使用不同的加载方法。只需几秒钟,您就可以在 google 上轻松找到这方面的示例。

更新:我过去支持jQuery.noConflict(true)与 RequireJS 一起使用的解决方法有效,但它需要对 jQuery 源代码进行非常小的修改,此后我找到了一种更好的方法来完成同样的事情而无需修改 jQuery。幸运的是,RequireJS 的作者 James Burke 也是如此,他已将其添加到 RequireJS 文档中:http ://requirejs.org/docs/jquery.html#noconflictmap

于 2013-02-26T20:33:20.133 回答
22
  1. 当您需要该依赖项时,路径告诉 require.js 在哪里查找。

    例如我有这样配置的东西:

    "paths": { 
        "jquery": "require_jquery"
    },
    "shim": {
        "jquery-cookie"  : ["jquery"],
        "bootstrap-tab"  : ["jquery"],
        "bootstrap-modal": ["jquery"],
        "bootstrap-alert": ["jquery"]
    },
    

    这意味着我每次在模块中

    define( ['jquery']
    

    requirejsrequire_jquery从主路径加载文件,而不是尝试加载 jquery.js。在您的情况下,它将加载 jQuery 源文件,然后该文件将在全球范围内可用。我个人不喜欢这种方法,因此在 require_jquery.js 文件中我这样做:

    define( ["jquery_1.7.2"], function() {
        // Raw jQuery does not return anything, so return it explicitly here.
        return jQuery.noConflict( true );
    } );
    

    这意味着 jQuery 将仅在我的模块中定义。(这是因为我编写了 Wordpress 插件,所以我可以包含我自己的 jQuery 版本而无需触及外部版本)

  2. 导出(从文档中读取应该是您正在使用的模块的名称,以便可以检测加载是否正确。这里解释了。因此,如果您想为下划线设置导出,它应该是_

  3. 正如我所解释的,jQuery 应该是全局的,如果你只是导入它,文件就会被执行并且 jQuery 是全局的

编辑 - 回答评论。

  1. 是的,我的意思是,您必须为 jQuery 导出 $ 或 jQuery,为骨干网导出 _。根据我从文档中得到的信息,这仅在某些边缘情况下需要,并且对于在全局命名空间中将自己声明为 jQuery 的库来说不是必需的。

    我认为 requirejs 在必须从 CDN 加载 jQuery 时需要它们。我认为 requirejs 首先尝试从 CDN 加载 jQuery,然后通过检查“导出”变量是否存在来检查它是否正确加载,如果不存在,它会从本地文件系统加载它(如果你当然,已经配置了后备)。当 requirejs 看不到 404 返回时,这是需要的。

  2. jQuery 是全局可用的,因为它被声明为全局的。如果你只是加载并执行 jQuery 脚本,你最终会得到两个全局变量,$并且jQuery(或者你可以像我一样做并避免这种情况)。在define()函数内部,您可以将 jQuery 别名为您想要的任何内容。

    define( [ 'jquery' ], function( jq ) {
        // jq is jquery inside this function. if you declared it 
        // globally it will be also available as $ and jQuery
    } );
    
于 2012-06-14T15:02:53.217 回答