1. 1. 一 先说采用var声明的这段代码
  2. 2. 二 看let这段代码,这段代码其实并不简单。
    1. 2.1. 1. 每次for循环i是同一个i吗?
    2. 2.2. 2. 既然每次i都是全新的变量,凭啥能记录循环的状态
  3. 3. 三 如果明白了,那如何用var实现第二段代码的功能?
Table of Contents ▼

关于ES6中let关键字的疑惑

一段熟悉的代码:

function createFunctions(){
    var result = new Array();

    for(var i = 0;i < 10;i++){
        result[i] = function(){
            console.log(i);
        };
    }
    return result;
}

var functions = createFunctions();

functions[2](); //10

上述代码输出结果为10。

觉得理所应当或者有所疑惑都很正常,请继续往下看:

function createFunctions2(){
    'use strict';
    var result = new Array();

    for(let i = 0;i < 10;i++){
        result[i] = function(){
            console.log(i);
        }
    }

    return result;
}

createFunctions2()[2](); //2

输出结果为2。

因为这里边涉及闭包的使用,对闭包理解不够透彻的人,很有可能认为是因为 闭包 的因素;但问题的实质在于 作用域

一 先说采用var声明的这段代码

ES6之前,JavaScript只有全局作用域和函数作用域,没有块级作用域的概念。但ES6中的let让JavaScript有了块级作用域。

在第一段代码中,变量i实际是在createFunctions中创建,在函数内部都可以进行访问(理解这一句其实问题就解决了)。

来看下具体过程:每次循环创建一个新函数result[i],由于var i的作用域是createFunctions,所以每次新建的函数都是引用的createFunctions活动对象中的i;这个i一直都是指向同一个地址;所以当i的值变化,result[x]中的i值自然也随之而变。

明白?

简单来说,因为var i的作用域是createFunctions,而非{},它一直都没被销毁。

我们只是用闭包来描述了具体的过程,但闭包不是问题的答案。

咱别晕在闭包上+_+;如需要补充闭包的知识,请认真理解 变量对象,活动对象作用域链这三个概念以及他们之间的关系,当你能用图把他们之间的关系描述清楚,那闭包就透彻了。

二 看let这段代码,这段代码其实并不简单。

for(let i =0;i < 10;i++){
    ...
}

1. 每次for循环i是同一个i吗?

当然不是,我们说过,let是块级作用域。let声明的变量只在它所在的代码块有效。

引用MDN的描述:

let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

所以,每进行一次循环,i都是一个全新的变量,i只在当前循环有效。

如果没问题,继续看下一个问题。

2. 既然每次i都是全新的变量,凭啥能记录循环的状态

当我们在for语句中使用let的时候,其实会自动对循环变量名进行绑定;然后创建一个局部作用域:在这里边,定义循环变量以及保存之前计算所得的循环变量的结果。

执行完循环体中的代码后,在进入下一次循环时,新建一个局部循环变量,利用上一次保存的最新结果进行初始化。

代码描述如下:(参看stackoverflow上的回答)

{
    let i;
    i = 0;
    __state = {i};
}
{
    let {i} = __state;
    if(i < 10){
        ... //循环体代码

        __state = {i};
    }
}

{
    let {i} = __state;
    i++;
    if(i < 10){
        ...

        __state = {i};
    }
}

{
    let {i} = __state;
    i++;
    ...
}

明白了吧,其实很简单,只是概念有点蹦。

三 如果明白了,那如何用var实现第二段代码的功能?

是的,关键在于模拟块级作用域,简单吧O(∩∩)O~~)

function createFunctions3(){
    var result = new Array();

    for(var i = 0;i < 10;i++){
        (function(c){
            result[i] = function(){
                console.log(c);
            }
        })(i);
    }
    return result;
}

var functions = createFunctions3();

functions[2](); //10