let 与块级作用域
作用域顾名思义指就是我们代码当中的一个成员它能够起作用的范围,在ES2015之前ECMAScript当中只有两种类型的作用域:全局作用域、函数作用域。在ES2015中又新增了一个块级作用域,块指的就是代码中用一对花括号{}所包裹起来的范围。例如 if 语句和for循环语句中的花括号都会产生这里所说的块,以前块是没有单独的作用域的,这就导致在块中定义的成员外部也可以访问到。这一点对于复杂代码是非常不利的,也是不安全的。有了块级作用域之后,就可以在代码当中通过一个新的关键词 let 去声明变量。它的用法和传统的 var 是一样的,只不过**通过 let 声明的变量只能在所声明的这个代码块中被访问到,也就是说在块级作用域定义的变量在外部是不能访问到的。**这样一个特性非常有利于声明 for 循环当中的计数器,传统的for循环如果出现了循环嵌套的情况,就必须为循环中的计数器设置不同的名称否则的话就会出现问题。
for (i = 0; i < 3; i++) {
for (i = 0; i < 5; i++) {
console.log(i)
}
}
// 0 1 2 3 4
例如一个for循环中嵌套了另一个for循环,并且两个循环的计数器都叫 i 。然后在内层循环中去打印 i ,按照预想的情况打印的结果应该是两次循环次数相乘的积。但实际上的结果是只打印了内部循环的次数,仔细分析原因其实也很简单。因为外层声明了这个 i 过后,内层循环再次声明了这个变量而且它们都是使用 var 去声明的,也就是说并不是一个块级作用域内的成员而是全局成员。内层所声明的这个 i ,就会覆盖掉之前外层所声明的 i 。等到内层循环执行完了过后 i 的值就是内部循环的最大值,对于外层来讲的话外层 拿到的 i 仍然是全局当中的 i 。所以所它的值如果大于外层的最大值,就不会满足外层的循环条件自然也就不会继续循环了。如果说使用的是 let 的话,就不会有这样的一个问题。因为let所声明的变量只能在当前循环体中所在的代码块中生效,这样内层的循环执行次数就等于内外部次数的积。**因为内部循环的i是块级作用域的局部成员,需要注意的是这里真正解决问题的实际上是内层循环当中的 let 。因为它把内部的 i 关进了一个盒子当中并不再去影响外部,就算把外部的 let 改成var也不会影响结果。**虽然let关键词解决了循环嵌套当中计数器重名导致的问题,但是最好不要去使用同名的计数器,因为这样的话不利于后期再去理解代码。
const chalk = require('chalk')
for (let i = 0; i < 3; i++) {
console.log(chalk.red(`外部:${i}`))
for (let i = 0; i < 5; i++) {
console.log(chalk.yellow(`内部:${i}`))
}
}
除此之外还有一个典型的应用场景就是循环去注册事件,在事件的处理函数中要求访问循环的计数器,这种情况下以前出现一些问题。在node环境中可以使用函数来模拟,首先定义一个elements数组,然后在数组当中去定义成员对象,每个对象都是一个空对象代表一个界面元素。然后遍历整个数并模拟为每一个元素添加一个 onClick 事件,实际上就是为每个对象添加一个 onClick 方法。在这个事件的处理函数当中,访问当前循环的计数器并打印出来。
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onClick = function () {
console.log(i)
}
}
elements[0].onClick() // 3
elements[1].onClick() // 3
这是因为每次打印的 i 都是全局作用域里的 i ,当循环结束过后 i 已经被累加到了 3 。所以说无论打印的是哪一个元素的 click 它的结果都是一样的,同时这也是闭包的一个典型应用场景。通过建立闭包就可以解决这样的一个问题,其实闭包也就是借助于函数作用域去摆脱全局作用域所产生的影响。现在有了块级作用域过后就不必要在这么麻烦了,这里只需要将 var 关键字修改成 let 。由此可以认识到块级作用域内部也是一种闭包的机制,因为在onClick 执行的时候循环在就结束了实际的 i 也早就销毁了,就是因为闭包的机制才可以拿到原本执行循环的时候的那个 i 所对应的值。另外在 for 循环中还有一个特别之处,因为在 for 循环内部实际上会有两层作用域,比如在下面这个案例中,i 在外层和内层被两次赋值却互不影响,因为它们在不同的作用域当中。它可以用 if 语句来模拟,可以看出循环体当中的 i 是独立的作用域,外层是 for 循环本身的作用域。
for (let i = 0; i < 3; i++) {
let i = 'foo';
console.log(i)
}
// foo foo foo
等价于
let i = 0;
if (i < 3) {
let i = "foo";
console.log(i)
}
i++;
if (i < 3) {
let i = "foo";
console.log(i)
}
i++;
if (i < 3) {
let i = "foo";
console.log(i)
}
...
除了会产生块级作用域限制以外, let 和 var 还有一个很大的区别就是 let 的声明不会出现提升的情况。传统的 var 去声明变量都会导致声明的变量提升到代码最开始的位置,如果在声明之前调用一个用var定义的变量,控制台并不会报错,而是打印了一个 undefined 这也就是说明在打印的时候 foo 此时就已经声明了只是还没有赋值而已,这种现象叫做变量声明的提升。其实在目前来看这样一个现象实际上是一个bug,在Es2015 let 的出现就是为了要解决这样一个问题。它从语法层面就要就开发者必须要先声明变量,再去使用变量否则的话就会报出一个为定义的错误。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!