在大多数计算机语言中,{}这样一对花括弧叫一个块级作用域,也就是一个执行环境。在一个执行环境中,执行环境内部的变量在作用域外部式无法被访问到的。执行环境内部倒是可以访问外部的变量。
但由于JS中没有块级作用域,只有函数作用域。所以类似于像for(;;){ }这样的作用域,实际上它还是在它的上层作用域之内,如果它上层作用域是global或window,那么实际上这个for循环还是在全局作用域下。
所以,在典型的JS面试题中出现的例子:
const Greeters = [] for (var i = 0 ; i < 10 ; i++) { Greeters.push(function () { return console.log(i) }) } Greeters[0]() // 10 Greeters[1]() // 10 Greeters[2]() // 10复制代码
由于,此处默认for语句是在全局作用域下,且for循环并不会生成块级作用域,因此,var声明的i变量也就自然在全局作用域之下,在for循环中又出现了一个匿名函数,这个匿名函数倒是有自己的函数作用域。所以,事实上,这个匿名函数先是写好放在那里,还没有被外部声明调用,它就仅仅地待在那里,当外部声明一个Greeters0想要调用这个函数作时,由于i是在全局作用域被var声明出来的,i已经事先被fior语句循环到等于10了,i=10已经事先固定地保存在全局作用域之中不动了。程序再才来调用的这个函数内部的i。这时,函数只需要去全局作用域环境去找到那个事先已经被固定下来的i=10的那个i就行了,所以,无论调用多少次,它只会去调用那个事先已经循环完毕,并被放入全局作用域下i=10那个i。换句话说,for循环语句的那个{}相当于可以视为没有写。
那么如何解决这个问题呢?
又因为JS中var来声明变量时,var声明出来的变量是在当前作用域环境之下, let声明出来的变量也是在当前作用域之下。不一样的是:let可以把类似于这种JS里面不是块级作用域的作用域(例如for(;;){})变成一个块级作用域,而var不会。并且var可以多次声明同一个变量名,而let不行
var a = 5; var a = 3; let b = 2; let b = 4; console.console.log(a); console.console.log(b); // Identifier 'b' has already been declared复制代码
所以,我们可以用ES6的语法let来创建一个块级作用域。
const Greeters = [] for (let i = 0 ; i < 10 ; i++) { Greeters.push(function () { return console.log(i) }) } Greeters[0]() // 0 Greeters[1]() // 1 Greeters[2]() // 2复制代码
此时,从外部去调用由于用let每创建一个i,就会去执行一遍内部的匿名函数,并保存起来push进数组Greeters[]里,当你想要调用某一个数组里的函数时,就直接Greetersi 调用对应的数组函数并console.log出当前的i了。