<!DOCTYPE HTML>
<html><head><script>
function test() {
array = [];
for (i = 0; i < 10; i += 1) {
array.push(
{a:'start' + i, b:function() {return 'end' + i;}}
);
}
return array;
}
window.onload = function () {
alert(test()[5].a + ' ' + test()[5].b());
}
</script></head><body></body></html>
在上面的代码中,此警报'start5 end10'。
我正在尝试将i的值传递给属性b中的函数,但无法弄清楚如何执行此操作。
我希望它能通过闭包访问i并提醒'start5 end5'。如何将i的值传递给属性b的函数,以便在执行test()[5] .b()时可以正确解析?
答案 0 :(得分:2)
你成了封闭怪物的受害者。没什么好害怕的,你不是第一个也不会是最后一个。封闭怪物总是很饿:)
您的解决方案无效,因为存储在对象数组中的未命名函数始终引用i的实时值(在循环结束时将为10)。
在JS中避免这种情况的方法是为b的每个值创建不同的函数上下文。 通常的方法是创建一个“工厂”函数,返回实际的函数调用,返回正确的b值。
function b_factory (b)
{
return function ()
{
return 'end'+b;
}
}
注意返回的函数如何引用超出其范围的b
变量。
正是这种能力为给定函数范围之外的变量提供了一个值,称为词法闭包。
在这种情况下,闭包位于b_factory
的参数中。
现在使用它:
function test() {
array = [];
for (i = 0; i < 10; i += 1) {
array.push(
{a:'start' + i, b:b_factory(i)}
);
}
return array;
}
这里发生的是:
b_factory
返回的未命名函数由array[i].b
引用,因此垃圾收集器不会销毁它b
的{{1}}参数依次由未命名的函数引用,因此b_factory的执行上下文(其中要找到b_factory
)也在内存中保持活动状态。 b
的值是传递给b
的参数的值,即调用时b_factory
的值到i
。一旦熟悉了这一点,就可以省去工厂函数的创建,并使用一个未命名的函数来提供闭包,如下所示:
b_factory
请注意,在这种情况下,内部未命名的函数有点过分。由于对象的b字段将是执行其余部分的常量,因此未命名的函数根本没有任何计算(从其上下文看,一切都是常量)。它与定义像这样的函数数组一样有用:
array.push(
{a:'start' + i, b:function(b) {return function() { return 'end'+b;}}(i)}
您可以将b字段定义为字符串,然后使用此代码:
function give_me_one () { return 1; }
function give_me_two () { return 2; }
// ;)
这里未命名的函数仍然创建相同的闭包,但返回一个常量字符串而不是函数。由于没有持久化对象来引用b参数,因此在未命名的函数执行后,闭包会立即被垃圾收集。
b字段现在是一个字符串,因此您应该在没有括号的情况下访问它,如下所示:
array.push(
{a:'start' + i, b:function(b) { return 'end'+b; }(i)}
当然,这只是一个演示封闭使用的练习。初始化对象的最有效方法就是处理b,就像处理a一样,即
console.log(test()[5].a + ' ' + test()[5].b);
答案 1 :(得分:1)
尝试这样的事情:
var getB = function(i) {
return function() {return 'end' + i;}
}
for (i = 0; i < 10; i += 1) {
array.push(
{a:'start' + i, b:getB(i)}
);
}
答案 2 :(得分:1)
它是理解JS中的范围的标准问题之一 - 你的函数引用了for循环中使用的i
(引用变量,而不是它的值),所以你在函数时获得该变量的最终值在循环结束后调用。它足以从另一个函数中创建该函数并将i
传递给工厂函数(JS中的变量按值传递)。另外,我没有在代码中看到变量i
的任何定义,你只是开始使用i
而没有初始化它 - 它将被绑定到顶级对象(本例中的窗口对象) ,变量将是可见的全局) - 避免在两个函数将开始修改全局变量时遇到一些奇怪问题的情况,它可能导致无限循环或循环结束到早期。
function test() {
array = [];
for (var i = 0; i < 10; i += 1) {
array.push({
a: 'start' + i,
b: (function (i) {
return function() {return 'end' + i;};
}(i))
});
}
return array;
}
答案 3 :(得分:0)
<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<title>Template Index</title>
</head>
<body>
<script>
function test() {
array = [];
for (i = 0; i < 10; i += 1) {
array.push(
(function (start, end) {
return {a:'start' + start, b:function() {return 'end' + end;}};
} (i, i))
);
}
return array;
}
window.onload = function () {
alert(test()[5].a + ' ' + test()[5].b());
}
</script>
</body>
</html>
代码{a:'start' + i, b:function() {return 'end' + i;}}
中的
a将获得正确的数字,因为当它循环通过0--9时它与字符串'start'连接
但是b生成抛出一个与闭包有效的函数,
所有函数都将在循环中访问相同的i,循环后为10
你需要创建一个新函数来保存i的快照,这样每个对象都有自己的副本
循环中i的值