【问题标题】:About the key word 'let' in JavaScript关于 JavaScript 中的关键字 'let'
【发布时间】:2017-05-20 00:49:36
【问题描述】:

var funcs=[];
for(let i=0;i<3;i++){
	funcs[i]=function(){
		
		return i;
	}
	
}
alert(funcs[1]);
alert(funcs[1]());
窗口警报 2 次。 第一个是这样的:

function (){
		return i;
	}

第二个这样

1

但我不知道为什么 funcs[1] 可以执行而不报告错误 'i is undefined';

【问题讨论】:

  • 如果你使用'var'而不是'let',它总是返回'3'

标签: javascript closures let


【解决方案1】:

因为在循环中创建的函数关闭创建时处于活动状态的lexical environment。该词法环境(在概念上)是一个对象,它包含在其中定义的局部变量(和其他一些东西),包括i 变量,在这种情况下,是为循环体的特定迭代创建的变量(因为非常特殊方式for 在其初始化程序中处理let 声明)。这就是“闭包”的概念,它是 JavaScript 的核心技术之一。即使执行超出了与给定词法环境相关联的范围(函数返回,我们继续循环的下一次迭代等),如果任何东西仍然具有对该环境对象的引用,就像它的所有对象一样继续存在。

由于for 在其初始化程序中处理let 的方式,funcs 中的每个条目都有自己的词法环境,因此有自己的i 副本。

当您调用其中一个函数时,会创建一个新的环境对象,并将其“外部”环境设置为附加到该函数的环境。当您在函数代码中引用 i 时,它会首先查看函数的环境,如果在其中找不到 i,它会查看外部环境——它找到它(在这种情况下)并使用它。

在你说过的评论中

如果你使用'var'而不是'let',它总是返回'3'

完全正确。使用vari 将被提升到与for 循环所在的函数关联的环境对象(或全局对象,如果这是全局代码)。所以在循环中创建的所有函数都共享同一个i,当你调用它们时,它的值是3

这是let/constvar 之间的本质区别之一:letconst 具有块作用域,而for 在其初始化程序中对let 具有特殊处理。

让我们在创建这些不同的环境对象时进行跟踪。假设这段代码:

funtion example() {
    const funcs = [];
    for (let i = 0; i < 3; ++i) {
        funcs[i] = function() {
            return i;
        };
    }
    funcs[1](); // 1
}
example();

当我们调用example 时,在const funcs = [] 之后但在for 循环开始之前,当前环境对象是为调用example 而创建的对象,所以我们在内存中有这样的东西(忽略一些细节):

+−−−−−−−−−−−−−−−−−−+ 当前环境>−−−−−−−−−−−−−−−−−−−−−−−−−−>| `example` 调用 | |环境对象 | +−−−−−−−−−−−−−−−−−−+ | [[Outer]] |>−−−>(创建`example`时的环境对象。) |函数 |>−+ +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ +−>| (数组) | +−−−−−−−−−−−−−+ |长度:0 | +−−−−−−−−−−−−−+

现在,for 循环开始:创建一个新的每次迭代环境对象作为“当前”环境对象,前一个环境对象作为其“外部”环境。一个i 变量在该新的每次迭代环境对象中创建,并赋予值0

+−−−−−−−−−−−−−−−+ 当前环境>−−>|迭代 0 | |环境对象 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+ | [[外]] |>−−−−−−−>| `example` 调用 | |我:0 | |环境对象 | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+ | [[Outer]] |>−−−>(创建`example`时的环境对象。) |函数 |>−+ +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ +−>| (数组) | +−−−−−−−−−−−−−+ |长度:0 | +−−−−−−−−−−−−−+

在循环迭代期间,我们创建一个函数并将其存储在funcs 中。该函数获取对当前环境对象的引用,它保留为[[Environment]](这是一个实现的东西,你实际上不会发现,如果你看这个函数,它在代码级别是不可访问的,就在JavaScript 引擎):

+−−−−−−−−−−−−−−−+ 当前环境>−+>|迭代 0 | / |环境对象 | +−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−−−−−−+ | `example` 调用 | + | [[外]] |>−−−−−−−−>|环境对象 | | |我:0 | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | [[Outer]] |>−−−>(创建`example`时的环境对象。) | |函数 |>−+ | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | +−>| (数组) | | +−−−−−−−−−−−−−+ | |长度:1 | +−−−−−−−−−−−−−−−−−−+ | | 0 |>−−−−−>|功能 0 | | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+ | | [[环境]] |>−−−−−−+ | +−−−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −+

现在这是在 for 初始化程序中巧妙处理 let 的地方(事实上,在 for 循环体中巧妙处理 letconst 也是如此):A 下一次迭代的>new环境对象被创建,i的值复制从上一次迭代的i到@ 987654360@ 进行下一次迭代。所以我们有:

+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−->|迭代 0 | | |环境对象 | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | `example` 调用 | | | [[外]] |>−−−+−−−>|环境对象 | | |我:0 | / +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ + | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | |功能 |>−−−+ | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | +−−>| (数组) | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+ | 当前环境>−+>|迭代 1 | | |长度:1 | +−−−−−−−−−−−−−−−−−−−+ | |环境对象 | | | 0 |>--->|功能 0 | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+ +−−−−−−−−−−−−− −−−−−+ | | [[外]] |>−+ | [[环境]] |>−+ | | i: 0 | +−−−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−+

然后,新环境中的i 递增为1,并创建一个新函数并将其存储在funcs 中,给我们:

+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−->|迭代 0 | | |环境对象 | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | `example` 调用 | | | [[外]] |>−−−+−−−>|环境对象 | | |我:0 | / +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ + | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | |功能 |>−−−+ | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | +−−>| (数组) | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ |当前环境>−+>|迭代 1 | | |长度:2 | +−−−−−−−−−−−−−−−−−−−+ | / |环境对象 | | | 0 |>--->|功能 0 | | / +−−−−−−−−−−−−−−−+ | | 1 |>−+ +−−−−−−−−−−−−−−−−−−−+ | + | [[外]] |>−+ +−−−−−−−−−−−−+ | | [[环境]] |>−−−+ | | |我:1 | | +−−−−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−+ | | | | | +−−−−−−−−−−−−−−−−−−+ | | | +−>|功能一 | | | | +−−−−−−−−−−−−−−−−−−+ | | | | [[环境]] |>−+ | | | +−−−−−−−−−−−−−−−−−−+ | | | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− --------------------------------------------------------------+ | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−−+

然后在该迭代结束时,我们再次为最后一次迭代做整个事情,并得到:

+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−->|迭代 0 | | |环境对象 | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | `example` 调用 | | | [[外]] |>−−+−−+ −>|环境对象 | | |我:0 | / / +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | | | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | | |功能 |>−−−+ | | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | | +−−>| (数组) | | +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−>|迭代 1 | | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+ | | |环境对象 | | | | 0 |>−−−−−>|功能 0 | | | +−−−−−−−−−−−−−−−+ | | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+ | | | [[外]] |>−+ | | 2 |>-+ | | [[环境]] |>−−−−−−+ | | |我: 1 | | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−+ | | | | | | | | | +−−−−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−+ | | +−>|功能 1 | | | | 当前环境>−+>|迭代 2 | | | +−−−−−−−−−−−−−−−−−−+ | | | / |环境对象 | | | | [[环境]] |>−−−+ | | | + +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−− -+ | | | | | | [[外]] |>−−−+
| | | | | | |我:2 | | +−−−−−−−−−−−−−−−−−−+ | | | | | +−−−−−−−−−−−−−−−+ +−−−>|功能二 | | | | | | +−−−−−−−−−−−−−−−−−−+ | | | | | | [[环境]] |>−+ | | | | | +−−−−−−−−−−−−−−−−−−+ | | | | | | | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ----------------------------------------------------------------+ | | | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- ----------------+

当我们调用funcs[1]() 时,会创建一个调用环境,并将其[[Outer]] 环境设置为函数的[[Environment]]。所以就在我们 return i 在那个函数中之前,我们有(省略一些细节):

+−−−−−−−−−−−−−−−+ 当前环境>−−>|致电 | | `funcs[1]()` | |环境对象 | +−−−−−−−−−−−−−−−+ | [[外]] |>−−+ +−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−+ | `示例`调用 | | +−−−>|环境对象 | | | +−−−−−−−−−−−−−−−−−−+ | | | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | |函数 |>−+ | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | +−>| (数组) | \ +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−+−−−>|迭代 1 | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+ | |环境对象 | | | 0 |>−−−−−>| (函数) | | +−−−−−−−−−−−−−−−+ | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+ | | [[外]] |>−+ | 2 |>-+ | | [[环境]] |>... | |我: 1 | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | | | | | +−−−−−−−−−−−−−−−−−−−+ | | +−>| (函数) | | | +−−−−−−−−−−−−−−−−−−−+ | | | [[环境]] |>−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | | | | | | +−−−−−−−−−−−−−−−−−−+ | | +−−−>| (函数) | | | +−−−−−−−−−−−−−−−−−−+ | | | [[环境]] |>... | | +−−−−−−−−−−−−−−−−−−+ | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−+

当函数查找i 时,它会查找当前环境对象。由于它不存在,因此它查找 [[Outer]] 对象。它在那里找到它,其值为1,所以它使用的就是这个值。

相反,如果我们使用var,则i 被提升到环境对象以调用example(其中funcs 是),所以在循环之后我们有这个(注意@987654375 @ 不再在每次迭代环境中):

+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−->|迭代 0 | | |环境对象 | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−+ | `example` 调用 | | | [[外]] |>−−+−−+−>|环境对象 | | +−−−−−−−−−−−−−−−+ / / +−−−−−−−−−−−−−−−−−−+ | | | | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | | |我: 3 | | | | |功能 |>−−−+ | | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | | +−−>| (数组) | | +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−>|迭代 1 | | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+ | | |环境对象 | | | | 0 |>−−−−−>|功能 0 | | | +−−−−−−−−−−−−−−−+ | | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+ | | | [[外]] |>−+ | | 2 |>-+ | | [[环境]] |>−−−−−−+ | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | | | | | | | | | | | | +−−−−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−+ | | +−>|功能 1 | | | |当前环境>−+>|迭代 2 | | | +−−−−−−−−−−−−−−−−−−+ | | | / |环境对象 | | | | [[环境]] |>−−−+ | | | + +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | | | | | | [[外]] |>−−−+ | | | | | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−−−+ | | | | | +−−−>|功能 2 | | | | | | +−−−−−−−−−−−−−−−−−−+ | | | | | | [[环境]] |>−+ | | | | | +−−−−−−−−−−−−−−−−−−+ | | | | | | | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ------------------------------------------------+ | | | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- ----------------+

这意味着当我们调用funcs[1]() 时,会为其创建一个新环境,它的[[Outer]] 设置为函数的[[Environment]],而在return i 之前我们有:

+−−−−−−−−−−−−−−−+ 当前环境>−−>|致电 | | `funcs[1]()` | |环境对象 | +−−−−−−−−−−−−−−−+ | [[外]] |>−−+ +−−−−−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−+ | | `示例`调用 | | +−−−>|环境对象 | | | +−−−−−−−−−−−−−−−−−−+ | | | [[Outer]] |>−−−>(创建`example`时的环境对象。) | | |我: 3 | | | |函数 |>−+ | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | +−>| (数组) | \ +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−+−−−>|迭代 1 | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+ | |环境对象 | | | 0 |>−−−−−>| (函数) | | +−−−−−−−−−−−−−−−+>−+ | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+ | | [[外]] | | 2 |>-+ | | [[环境]] |>... | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−−+ | | | | | | +−−−−−−−−−−−−−−−−−−−+ | | +−>| (函数) | | | +−−−−−−−−−−−−−−−−−−−+ | | | [[环境]] |>−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | | | | | | +−−−−−−−−−−−−−−−−−−+ | | +−−−>| (函数) | | | +−−−−−−−−−−−−−−−−−−+ | | | [[环境]] |>... | | +−−−−−−−−−−−−−−−−−−+ | | | | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−+

所以函数在查找i时,在当前环境中没有找到,在第一个[[Outer]]环境中也没有找到,但是在第二个环境中确实找到了 [[Outer]] 环境,其值为3,这就是它使用的值。

【讨论】:

  • 谢谢。但我还有一些问题。'i 没有连接到为循环迭代创建的执行上下文,它连接到包含你的 for 循环的执行上下文'是什么意思?什么时候发生“所以 funcs 中的每个条目都有自己的 i 副本”?
  • 非常感谢。但是在中国已经很晚了。明天早上我会读清楚的。等不及了。
  • 图片很清楚,我想我已经明白了。我真的很感谢你的帮助。
  • 另外,如果你在循环中使用 function.bind(this,i),“this”代表什么?你图片中的每个迭代 Env 对象?
  • @zyMacro:不,this 在整个函数调用中都是相同的,this 绝不是对环境对象的引用。 (JavaScript 中没有直接引用环境对象的机制,事实上,虽然它在概念上存在,但只要代码行为正确,实现就可以完全优化它。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多