当我在 Node JS 中调用 modernizr.build 时非常不寻常的作用域行为

Very unusual scope behaviour when I call modernizr.build in Node JS

我正在尝试同时使用 Modernizr 构建多个 json,但它似乎超出了我的功能范围。 很难解释所以看看这个例子,如果你不相信我,试试看:

[1,2,3,4,5].forEach(function(i){
    require("modernizr").build({}, function (result) {
        console.log(i);
    });
})

输出:

5
5
5
5
5

而不是预期的 1、2、3、4、5,就像任何类似的函数一样。

在我使用类似 ECMAScript 的语言编写代码的所有年中,我以前从未遇到过这种行为,并且围绕这样的想法构建了我的项目(和以前的项目),即你不能像那样破坏函数的范围。

它会破坏任何基于承诺甚至简单回调的系统。 这让我困惑了一整天,我找不到合适的解决办法。 我什至很难概念化导致这种情况发生的原因。 请帮忙。

编辑:

好的,看来你们都被 forEach 挂断了…… 这是另一个示例,可以使它更清楚一点:

function asd(i){
    require("modernizr").build({}, function (result) {
        console.log(i);
    });
}

asd(1);
asd(2);
asd(3);
asd(4);

产出

4
4
4
4

到底发生了什么事?

功能块是异步调用的,所以这种行为是预料之中的,因为这个调用比你的 foreach 慢得多,所以当你到达 function (result) {} 块时 i 已经五

与此处 Node.JS: How to pass variables to asynchronous callbacks? 中描述的问题完全相同,您应该能够使用相同的解决方案

[1,2,3,4,5].forEach(function(i){
    (function(i) {
        require("modernizr").build({}, function (result) {
            console.log(i);
        });
    })(i);
})

未经测试,但类似的东西应该可以工作

Modernizr 特有的问题与全局变量被破坏有关。

build 命令基本上是一个大型 requirejs 配置函数,全部由大型配置对象提供支持。在函数的顶部建立了一些基本的东西,它们总是正确的

{
  optimize: 'none',
  generateSourceMaps: false,
  optimizeCss: 'none',
  useStrict: true,
  include: ['modernizr-init'],
  fileExclusionRegExp: /^(.git|node_modules|modulizr|media|test)$/,
  wrap: {
    start: '\n;(function(window, document, undefined){',
    end: '})(window, document);'
  }
}

然后,由于 Modernizr 可以在浏览器和节点中工作而无需更改,因此需要有一种方法让它知道它是否应该通过文件系统或通过 http 加载其依赖项。所以我们在环境检查中添加了更多选项,例如 basePath

if (inBrowser) {
  baseRequireConfig.baseUrl = '/i/js/modernizr-git/src';
} else {
  baseRequireConfig.baseUrl = __dirname + '/../src';  
}

此时,配置对象被传递到 requirejs.config,它连接了 require 并允许我们开始调用 build

最后,在创建所有这些之后,我们有一个构建函数,它最终还会再次修改配置对象以进行构建特定设置(构建中的实际检测,正则表达式去除一些 AMD crud,等)。

所以这是最终发生的事情的超级简化伪代码版本

var config = {
  name: 'modernizr'
}

if (inBrowser) {
  config.env = 'browser';
} else {
  config.env = 'node';    
}

requirejs.config(config);

module.exports = function(config, callback) {
  config.out = function (output) {
    //code to strip out AMD ceremony, add classPrefix, version, etc

    callback(output)
  }

  requirejs.optimize(config)
}

发现问题了吗?

因为我们在 运行 之前接触了配置对象的 .out 方法(其范围是整个模块,因此它的上下文保存在 build() 调用之间)异步 require.optimize 函数,每次调用 build 时,您传递的回调都会重写 .out 方法。

这应该会在几个小时内在 Modernizr 中得到修复