今天做一个循环想得到1,2,3...10的输出结果。自己写的放弃了,然后看到了这么一个方案,但不是很理解,不知道自己哪一方面知识的欠缺,求大神解答!!!
var funcs = [];
for (var i=0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs.forEach(function(func) {
func(); // outputs 0, then 1, then 2, up to 9
});
自己访问自己的变量i,在每一个循环中都传入一个i值???
知识点:
词法作用域
闭包
立即执行函数表达式
一步一步来,首先,你写的应该是这样:
for (var i=0; i < 10; i++) {
funcs.push(function(i) {
return function() {
console.log(i);
}
});
}
分析:9从哪里来?9为循环i的最终值。函数在循环结束后才调用,所以每次都是9;
但不符合你的目的(从1,2,3 ... 9)依次打印出来,问题出在它们被封闭在一个共享的全局作用域中,实际上就只有一个i,所以都是9。
还是不明白?看这里:假设循环在全局环境中,那么循环中的i(var i=0)便会被提到最上头,相对于循环中的函数, i 便是自由变量(存放在作用域链的[[Scope]]属性中),而循环中的函数都处在同一个父上下文中,它们指向了同一个[[Scope]]属性,所以 i 为同一个。。
思考:那我们给创建各自的作用域不就行了,这样就引入了立即执行函数表达式(题中为匿名函数表达式)来创建各自的作用域。所以改进代码为:
for (var i=0; i < 10; i++) {
funcs.push((function(i) {
return function() {
console.log(i);
}
}()));
}
but,结果还是一样都是9,why?因为我们创建了空白的作用域,我们要传入东西才行啊,so:
for (var i=0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
这样就将 i 传进去了,就是这么任性。。。
补充1:ES6已经有let可以创建块作用域了,所以,上面可以这么写:
for (let i=0; i < 10; i++) {
funcs.push(function(i) {
return function() {
console.log(i);
}
});
}
建议你看看刚出不久的《你不知道的JavaScript上卷》,这部分讲得很透彻~
补充2:看完3楼,发现漏了i++。。i为10
1)js中没有变量的作用域在函数而非块作用域
2)闭包问题
如果你这样写,那么输出的都是9,因为i的值为for循环的变量值
var i;
for (i=0; i < 10; i++) {
funcs.push(function() {
return i;
});
}
定义了一个立即执行函数,这个函数返回一个新的函数对象,因为闭包的关系,其能访问传入的变量i
for (var i=0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
错误分析
for (var i=0; i < 10; i++) {
funcs.push(function() {
return i;
});
}
以上代码控制台会输出10个10(注意!i最终的值是10而不是9,所以不会输出10个9)。这是因为funcs数组保存的是一个匿名函数的定义,函数中的i仍是i,并没有被赋值为1,2,3...当遍历数组依次执行匿名函数时,函数这个时候才会寻找变量i的赋值,而此时i的值为10,所以最后输出的是10个10。
解决方案一
for (var i=0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs数组依然保存着匿名函数的定义,但是不同的是,这时每个匿名函数都分别给它传了一个参数,这个传入的参数依次是1,2,3,...9。所以最后遍历执行匿名函数时,value的值取传入的参数值,即输出1,2,3...9。
解决方案二
将函数移到循环的外面。
var funcs = [];
function print(i){
return function(){console.log(i);}//不用return的话函数会立即执行。
}
for(var i = 0; i < 10; i++){
funcs.push(print(i));
}
综上,你欠缺的是闭包的相关知识。
其实前面有几位兄台已经说得很清楚了。我这里就再通俗地解释一下。
首先看下失败的代码:
var funcs = []
for (var i = 0; i < 3; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs[0]() // 3
funcs[1]() // 3
funcs(2)() // 3
为什么这样呢?其实如果剥离掉for
语句,以上代码会像这样子:
var funcs = [],
i = 0
funcs.push(function() {
console.log(i)
})
i++
funcs.push(function() {
console.log(i)
})
i++
funcs.push(function() {
console.log(i)
})
i++
funcs[0]() // 3
funcs[1]() // 3
funcs[2]() // 3
为什么会输出上面的结果显而易见了吧。
那么要达到所需要的要求怎么做呢?通常就在push
方法中传入一个自执行函数,
并且将i
作为自执行函数的参数,以供作用于它内部。
var funcs = [],
i = 0
funcs.push((function(i){
return function() {
console.log(i)
}
}(i)))
i++
funcs.push((function(i){
return function() {
console.log(i)
}
}(i)))
i++
funcs[0]() // 0
funcs[1]() // 1
现在还原到for
语句的形式:
var funcs = []
for (var i = 0; i < 3; i++) {
funcs.push((function(i) {
return function() {
console.log(i)
}
}(i)))
}
// 这里会依次输出 0, 1, 2
funcs.forEach(function(f){
f()
})
Over~