在for循环中初始化的变量的作用域规则 [英] Scoping rules for variables initialized in a for loop

查看:116
本文介绍了在for循环中初始化的变量的作用域规则的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


可能重复:

Javascript闭包内部循环 - 简单实用示例





<我在我的一个项目中玩setTimeout以限制向DOM添加元素(因此UI在页面加载期间不会冻结)。但是,我遇到了一些让我感到困惑的事情。鉴于此代码:

I'm playing around with setTimeout in a project of mine in order to throttle the adding of elements to the DOM (so UI won't freeze during page loading). However, I've encountered something a bit puzzling to me. Given this code:

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function() {
        console.log("in timeout i is: " + i + " j is: " + j);
    }, i * 1000);
}

我得到以下输出:

i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14
in timeout i is: 5 j is: 14

超时中 i 的值为5是显而易见的,因为我在for循环初始化范围内。但是,对于所有超时输出, j 的结果是14?我原以为 j 会在超时中输出10,11,12,13,14,因为它在循环中作用域。我怎么能实现这个结果?

That the value of i in the timeout is 5 is obvious since i is scoped in the for loop initialization. However, how come j is 14 for all timeout outputs? I would have thought that j would have output 10, 11, 12, 13, 14 in the timeout since it is scoped within the loop. How could I achieve that result?

推荐答案

那是因为,在JavaScript中, var 具有功能范围

That is because, in JavaScript, var has function scope.

var 声明将被提升至当前执行上下文的顶部。也就是说,如果它在函数内部, var 将是函数执行上下文中的作用域,否则是程序(全局)执行上下文。

var declarations will be hoisted up to the top of the current execution context. That is, if it is inside of a function, the var will be the scoped inside the function's execution context, otherwise the program (global) execution context.

ECMAScript 2015(又名ES6)介绍 允许你创建块范围变量,但由于它没有被广泛支持,我只是留下链接以供参考。

ECMAScript 2015 (a.k.a. ES6) introduces let which lets you create block scope vars, but as it is not widely supported I'll just leave the link for reference.

一种解决方法,仍然使用 var 并让它在循环中限定,是创建一个新的执行上下文,知道为关闭

An workaround, to still use var and have it "scoped" inside the loop, is to create a new execution context, also know as closure:

function callbackFactory(i, j) {
    // Now `i` and `j` are scoped inside each `callbackFactory` execution context.
    return function() { // This returned function will be used by the `setTimeout`.
       // Lexical scope (scope chain) will seek up the closest `i` and `j` in parent
       // scopes, that being of the `callbackFactory`'s scope in which this returned
       // function has been initialized.
       console.log("in timeout i is: " + i + " j is: " + j);
    };
}
for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout( callbackFactory(i, j), i * 1000);
}

因为我确定了 i j 在回调范围内,它们将在 setTimeout 内返回与它们时相同的值传递给 callbackFactory

As I scoped both i and j inside the callback scope, they will return the same values inside the setTimeout than they had when they were passed to callbackFactory.

参见 现场演示

另一种做同样事情的方法是创建内的 IIFE c>循环。这通常更容易阅读,但JS(H | L)int会对你大喊大叫。 ;)这是因为在循环中创建函数被认为对性能不利。

Another way to do the same thing is to create an IIFE inside the for loop. This is usually simpler to read but JS(H|L)int will yell at you. ;) This is because creating functions inside a loop is considered bad for performance.

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    (function(i, j) { // new execution context created for each iteration
        setTimeout(function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        }, i * 1000);
    }(i, j)); // the variables inside the `for` are passed to the IIFE
}

上面我在每次迭代中,在中为创建了一个新的执行上下文。 (演示

Above I've created a new execution context inside the for in each iteration. (Demo)

混合第一个使用上述IIFE的方法( callbackFactory ),我们甚至可以做出第3个选项:

Mixing the first approach (callbackFactory) with the IIFE above, we could even make a 3rd option:

for(var i = 0; i < 5; i++) {
    var j = i + 10;
    console.log("i is: " + i + " j is: " + j);
    setTimeout(function(i, j) {
        return function() {
            console.log("in timeout i is: " + i + " j is: " + j);
        };
    }(i, j), i * 1000);
}

这只是使用IIFE代替 callbackFactory 函数。这似乎不容易阅读,仍然在中为循环创建函数,这对性能有害,但只是注意到这也是可能的并且工作

This is simply using an IIFE in the place of the callbackFactory function. This doesn't seem very easy to read and still creates functions inside the for loop which is bad for performance, but just noting that this is also possible and works.

这3种方法在野生。 =]

These 3 approaches are very commonly seen in the wild. =]

哦,差点忘了回答主要问题。只需将 callbackFactory 放在与 for 循环相同的范围内,然后代替确定的范围我在里面,让范围链寻找外部范围的 i

Oh, almost forgot to answer the main question. Just put the callbackFactory in the same scope as the for loop, then instead of scoping the i inside of it, let the scope chain seek the i of the outer scope:

(function() {
    var i, j;
    function callbackFactory(j) {
    // the `j` inside this execution context enters it as a formal parameter,
    // shadowing the outer `j`. That is, it is independent from the outer `j`.
    // You could name the parameter as "k" and use "k" when logging, for example.
        return function() {
           // Scope chain will seek the closest `j` in parent scopes, that being
           // the one from the callbackFactory's scope in which this returned
           // function has been initialized.
           // It will also seek up the "closest" `i`,
           // which is scoped inside the outer wrapper IIFE.
           console.log("in timeout i is: " + i + " j is: " + j);
        };
    }
    for(i = 0; i < 5; i++) {
        j = i + 10;
        console.log("i is: " + i + " j is: " + j);
        setTimeout( callbackFactory(j), i * 1000);
    }
}());
/* Yields:
i is: 0 j is: 10  
i is: 1 j is: 11  
i is: 2 j is: 12  
i is: 3 j is: 13  
i is: 4 j is: 14  
in timeout i is: 5 j is: 10  
in timeout i is: 5 j is: 11  
in timeout i is: 5 j is: 12  
in timeout i is: 5 j is: 13  
in timeout i is: 5 j is: 14 */

小提琴

请注意,我已移动 i j 仅为了可读性而向范围顶部声明。它与具有相同的效果,这将由解释器提升。

Note that I've moved the i and j declarations to the top of the scope solely for readability. It has the same effect as for (var i = [...], which would be hoisted up by the interpreter.

这篇关于在for循环中初始化的变量的作用域规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆