为什么要使用替代的RequireJS define方式:define(function(require) { ... })。

19

我曾见过有人在 require js 中使用与官方文档或许多教程描述不同的"语法"来定义模块。

通常的定义 "语法":

define(['module/first'], function (firstModule) {
   //Module code with a dependency on module/first goes here.
});

"语法的备用定义":

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

文件:config.js:

require.config({
   paths: {
      jquery:      '../assets/js/libs/jquery'
   }
});
require(['app']);

文件:app.js:

define(function(require) {
     var FirstModule = require('modules/first');
     //Module code with a dependency on module/first goes here.

使用这种备选语法的优点和缺点是什么?

1个回答

41
我认为你的解释有些误导:在这两种情况下,你都将拥有一个顶层require调用,其中包含一个data-main属性,用于指定启动需要不同模块的文件。因此,通常你的HTML代码将类似于:
<script data-main="app/config" src="assets/js/libs/require.js"></script>

然后,在这两种情况下,您都会有一个文件app/config,它设置了您的配置(尽管您可以直接在HTML中进行此操作),更重要的是调用require来引用您的模块:

require.config({
  paths: {
    jquery:      '../assets/js/libs/jquery'
  }
});
require(['app']);

现在,我们来定义带有依赖关系的模块时,这些样式会有所不同。在AMD风格中,您将模块名称(路径)作为数组传递,并传递一个函数,该函数接受相同数量的参数:

app.js

define(['module/first', 'module/second', 'module/third'], function (firstModule, secondModule, thirdModule) {
  // use firstModule, secondModule, thirdModule here
});
在简化的CommonJS语法中,你只需要将require传递到define中,然后内联地引用所需的任何模块: app.js
define(function(require) {
  var firstModule = require('modules/first');
  var secondModule = require('modules/second');
  var thirdModule = require('modules/third');
  // use firstModule, secondModule, thirdModule here

}

回到您最初的问题,CommonJS风格相对于amd风格的优势应该是清晰明了的。

首先,使用传统语法时,如果需要许多模块,很容易错误地将模块分配给错误的变量名。考虑以下常见情况:

define(['jquery', 'underscore', 'backbone', 'modules/first', 'modules/second', 'modules/third', 'i18n', 'someOtherModule'], function ($, _, Backbone, first, second, third, I18n, someOtherModule) {
  // ...
});

很明显,当我们向此列表中添加新模块时,我们必须非常小心,以确保相应的新函数参数出现在正确的位置,否则我们可能会将jQuery分配给 Backbone 等。在某些情况下,这可能会导致非常微妙的错误,难以追踪。

现在考虑CommonJS语法:

define(function(require) {
  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var firstModule = require('modules/first');
  var secondModule = require('modules/second');
  var thirdModule = require('modules/third');
  var I18n = require('i18n');
  var someOtherModule = require('someOtherModule');
  // ...
}

请注意:

  1. 模块与变量名的配对非常清晰。
  2. require语句的顺序不重要,因为变量名是单独配对而不是映射到数组和函数之间。
  3. 模块不需要先分配。它们可以在任何地方分配,只要在实际使用模块之前即可。

这些只是我想到的一些原因,我相信还有其他原因。基本上,如果您只有一两个依赖项,则任何语法都可以正常工作。但是,如果您具有复杂的模块依赖网络,则CommonJS语法可能更可取。

请注意,在RequireJS文档中,他们提到这个小细节

并非所有浏览器都会给出可用的Function.prototype.toString()结果。截至2011年10月,PS 3和旧版Opera Mobile浏览器不支持。这些浏览器更可能需要一个针对网络/设备限制进行优化的模块构建,因此只需使用知道如何将这些文件转换为标准化依赖项数组形式的优化程序进行构建,例如RequireJS优化程序。

但是这不是一个主要问题:

由于不能支持toString()扫描的浏览器数量非常少,因此可以安全地将这些带糖形式用于所有模块,特别是如果您喜欢将依赖项名称与将保存其模块值的变量对齐。


1
我同意 commonjs 风格的优点。您有任何想法或想法,为什么在教程和 requirejs 文档中青睐 amd 风格?如果结果相同,为什么不建议每个人都使用 commonjs 风格?唯一的理由是“小警告”吗? - joholo
1
我不能确定,但我认为这主要是历史原因:amd格式是标准,而简化的CommonJS只是在其上添加了一个“糖”层。如果你要教授RequireJS,那么选择具有更长历史和更广泛采用的格式是有道理的,即使“糖化”版本更简单且更好。请注意,我在这里大多只是假设。 - Chris Salzberg
1
你也可以这样认为,通过强制在模块顶部(在任何其他内容之前)定义所有依赖项,amd格式使那些依赖项更容易一眼看出。尽管如果你使用CommonJS格式并继续将依赖项放在模块顶部,那么这并不是真正的问题。 - Chris Salzberg
4
这是一个很棒的答案,只要RequireJS文档也能像这样清晰易懂就好了。 - Jed Richards
@shioyama 最佳答案是我见过的最棒的答案 :) - Kunj
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接