关于javascript:当我在Node JS中调用modernizr.build时,非常不寻常的范围行为

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

我正在尝试使用Modernizr一次构建几个jsons,但它似乎打破了我的功能范围。
这很难解释,所以看看这个例子,如果你不相信我,试一试:

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

输出:

1
2
3
4
5
5
5
5
5
5

而不是预期的1,2,3,4,5,以及任何类似的功能。

在我使用ECMAScript语言编写的所有年份之前,我没有遇到过这种行为,并且已经构建了我的项目(以及之前的项目)围绕着这样的想法,即你不能破坏这样的函数范围。

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

编辑:

好吧,看来你们都挂在了forEach上......
这是另一个让它更清晰的例子:

1
2
3
4
5
6
7
8
9
10
function asd(i){
    require("modernizr").build({}, function (result) {
        console.log(i);
    });
}

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

输出

1
2
3
4
4
4
4
4

究竟发生了什么?


Modernizr特有的问题不得不使用全局变量。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  optimize: 'none',
  generateSourceMaps: false,
  optimizeCss: 'none',
  useStrict: true,
  include: ['modernizr-init'],
  fileExclusionRegExp: /^(.git|node_modules|modulizr|media|test)$/,
  wrap: {
    start: '
;(function(window, document, undefined){'
,
    end: '})(window, document);'
  }
}

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

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

此时,配置对象被传递到requirejs.config,这需要连线,并允许我们开始调用build

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

所以这是一个超级简化的伪代码版本,最终发生了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
}

发现问题?

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

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


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

与Node.JS中描述的问题完全相同:如何将变量传递给异步回调?在这里你应该能够使用相同的解决方案

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

未经测试但有些像这样应该有效