首页 > 有关var变量的变态问题。

有关var变量的变态问题。

今天做一个循环想得到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值???


知识点:

  1. 词法作用域

  2. 闭包

  3. 立即执行函数表达式

一步一步来,首先,你写的应该是这样:

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~

【热门文章】
【热门文章】