Require.js伤害了我的大脑。关于它加载脚本/模块的方式的一些基本问题

时间:2012-06-14 10:48:01

标签: javascript jquery module requirejs

我们假设这是我的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配置选项允许您引用脚本,并在HTML中引用<script>标记。假设是这种情况,我是否仍然需要在下面的实际require语句中使用$或下划线和_等jQuery脚本别名?考虑到如果您使用标准<script>标记引用jQuery,$可以在整个脚本中自动使用,这似乎很奇怪。使用paths

  2. 不应该是一样的
  3. 我是shim配置选项的新手,据我所知,已经取代了已弃用的order!插件。 exports属性实际上做了什么?它似乎没有为脚本创建别名;例如,如果我将下划线的exports设置为"whatever",然后尝试console.log(whatever),那么它是未定义的。那有什么意义呢?

  4. 如何在全局范围内正确使用jQuery这样的脚本?也就是说,能够在我的App.js模块或我的“app”文件夹中的任何其他模块中使用$别名的正确方法是什么?我是否必须在每个单独的模块中使用jQuery并且每次都需要别名$?或者我在这里以正确的方式完成它的方式?

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

2 个答案:

答案 0 :(得分:22)

  1. 路径告诉require.js何时需要依赖项。

    例如我的配置如下:

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

    这意味着我每次都在模块中

    define( ['jquery']
    

    requirejs从主路径加载文件require_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. 导出(从文档中读取应该是您正在使用的模块的名称,以便在加载正确时可以检测到它。Here已解释。所以如果要设置导出对于下划线,它应该是_

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

  4. 编辑 - 回答评论。

    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
      } );
      

答案 1 :(得分:22)

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

要记住的一个重要事实是,除非shim配置包含exports值,否则不会执行该配置上的任何init方法。依赖性加载器必须在初始化该模块之前找到模块的值(这是exports指定的值),这就是为什么如果该模块存在垫片init则需要该属性。 / p>

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

更新2:关于requireJS谷歌小组的最近一个问题让我意识到我的解释可能有些误导,所以我想澄清一下。 RequireJS只会重新使用一个shimmed依赖,如果它是通过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 (_) {});var _ = require('underscore');时,RequireJS将重新加载下划线库,而不是重新使用先前定义的window._,因为只要requireJS知道,你之前从未加过下划线。当然,它可以检查是否已在根范围上定义_,但无法验证已存在的_是否与在此范围内定义的paths相同你的prototype配置。例如,默认情况下,jquerywindow.$都会将自己分配给 <script src='lib/underscore.js'></script> <script src='lib/require.js' data-main='main.js'></script> <script src='lib/underscore.js'></script> ,如果requireJS假定为&#39;窗口。$&#39;是jQuery,当它实际上是原型时,你将会处于糟糕的状态。

所有这些意味着,如果您混合搭配脚本加载样式,那么您的页面将会出现以下内容:

exports

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

基本上,必须通过requireJS加载库,以便requireJS了解它。但是, next 时间你需要下划线,requireJS会go嘿,我已经加载了它,所以只需交回define(['jquery'], function (jQuery) { 值的任何值,不要担心关于加载另一个脚本。&#34;

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

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

的问题
    return jQuery;
});

位于顶部,

jQuery

在底部。我之所以有这样的原因&#39; jquery&#39;映射到$而不是(function ($) { // plugin code here })(jQuery); 是因为我注意到这些天的大多数插件都包含在这样的闭包中:

$

注意预期的范围是一个好主意。你当然可以映射&#39; jquery&#39;但是直接到jQuery,假设插件不希望找到$而不是jQuery.noConflict(true)。这只是基本的AMD包装器 - 更复杂的包装器通常会尝试检测正在使用哪种加载器(commonJS vs AMD与常规ol&#39;全局)并根据结果使用不同的加载方法。你可以在google上用几秒钟轻松找到这个例子。

更新:我曾经支持使用带有RequireJS的{{1}}的解决方法有效,但是它需要对jQuery源进行非常小的修改,而且我已经想出了一个更好的方法来完成同样的事情而不需要修改jQuery的。幸运的是,RequireJS的作者James Burke也将其添加到RequireJS文档中:http://requirejs.org/docs/jquery.html#noconflictmap