轻松理解Javascript变量的相关问题(2)

事实上,这个问题的答案是,循环本身及三次timeout回调均共享唯一的变量i。当循环结束执行时,i的值为3(因为messages.length的值为3),此时回调尚未被触发。

所以当第一个timeout执行时,调用cat.say(messages[i]) ,此时i的值为3,所以猫咪最终打印出来的是messages[3]的值亦即undefined。

解决这个问题有很多种方法( 这里有一种 ),但是你想,var作用域规则接连给你添麻烦,如果能在第一时间彻底解决掉这个问题多好啊!

let是更完美的var

JavaScript的设计错误(其它语言也有,奈何JavaScript太突出)多半不能被修复。保持向后兼容性意味着永不改变JS代码在Web平台上的行为,即使连标准委员会都无权要求修复JavaScript中自动插入分号这种怪异的特性;浏览器厂商也从来不会做出突破性的改变,因为如此一来伤害的是他们的忠实用户。

所以大约十年以前,Brendan Eich决定修复这个问题,但只有唯一的解决方案。

他添加了一个新的关键词:let。let与var一样,也可以用来声明变量,但它有着更好的作用域规则。

它看起来是这样的:

let t = readTachymeter();

或者这样的:

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

let与var还是有不同之处的,所以如果你只是在代码中将var全局搜索替换为let,一些依赖var声明的独特特性(可能你不是故意这样写)的代码可能无法正常运行。但对于绝大多数代码来说,在ES6的新代码模式下,你应该停止使用var声明变量,能使用let就用吧!从现在起,请记住这句口号:“let是更完美的var”。

那到底let和var有什么不同呢?非常高兴你提出这个问题!

这一规则可以帮助你捕捉bug,除了NaN错误以外,每一个异常都会在当前行抛出。

let声明的变量拥有块级作用域。也就是说用let声明的变量的作用域只是外层块,而不是整个外层函数。

let声明仍然保留了提升的特性,但不会盲目提升。在runTowerExperiment这个示例中,通过将var替换为let可以快速修复问题,如果你处处使用let进行声明,就不会遇到类似的bug。

let声明的全局变量不是全局对象的属性。这就意味着,你不可 以通过window.变量名的方式访问这些变量。它们只存在于一个不可见的块的作用域中,这个块理论上是Web页面中运行的所有JS代码的外层块。

形如for (let x...)的循环在每次迭代时都为x创建新的绑定。

这是一个非常微妙的区别,拿我们的会说话的猫的例子来说,如果一个for (let...)循环执行多次并且循环保持了一个闭包,那么每个闭包将捕捉一个循环变量的不同值作为副本,而不是所有闭包都捕捉循环变量的同一个值。

所以在会说话的猫示例中,也可以通过将var替换为let修复bug。

这种情况适用于现有的三种循环方式:for-of、for-in、以及传统的用分号分隔的类C循环。

let声明的变量直到控制流到达该变量被定义的代码行时才会被装载,所以在到达之前使用该变量会触发错误。举个例子:

function update() { console.log("当前时间:", t); // 引用错误(ReferenceError) ... let t = readTachymeter() }

不可访问的这段时间变量一直处于作用域中,但是尚未装载,它们位于临时死区(Temporal Dead Zone,简称TDZ)中。我一直想用科幻小说来类比这个脑洞大开的行话,但是还没想好怎么搞。

(脆弱的性能细节:在大多数情况下,查看代码就可以区分声明是否已经执行,所以事实上,JavaScript引擎不需要在每次代码运行时都额外执行 一次变量可访问检查来确保变量已经被初始化。然而在闭包内部有时不是透明的,这时JavaScript引擎将会做一个运行时检查,也就意味着let相对var而言比较慢。)

(脆弱的平行宇宙作用域细节:在一些编程语言中,一个变量的作用域始于声明之处,而非前后覆盖整个封闭代码块。标准委员会曾考虑过将这种作用域准则赋予let关键词,但是一旦使用这种准则,原本提前使用变量的语句会导致引用错误(ReferenceError),现在该语句不位于let t的声明作用域中,根本不会引用此处的变量t,而是引用外层作用域的相应变量。但是这个方法无法与闭包和函数提升很好得结合,所以该提案最终被否决了。)

用let重定义变量会抛出一个语法错误(SyntaxError)。

这一条规则也可以帮助你检测琐碎的小问题。诚然,这亦是var与let的不同之处,当你全局搜索var替换为let时也会导致let重定义语法错误,因为这一规则对全局let变量也有效。

如果你的多个脚本中都声明了相同的全局变量,你最好继续用var声明这些变量。如果你换用了let,后加载的脚本都会执行失败并抛出错误。

或者你可以考虑使用ES6内建的模块机制,后面的文章中会详细讲解。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwdzfd.html