[ES6深度解析]13:let const

当Brendan Eich在1995年设计了JavaScript的第一个版本时,他犯了很多错误,包括从那时起就成为该语言一部分的一些错误,比如Date对象和当你不小心将它们相乘时对象会自动转换为NaN。然而,事后看来,他做对的事情都是非常重要的事情:对象;原型;具有词法作用域的一级函数;默认可变性。这种语言很好。比大家一开始意识到的要好。

尽管如此,Brendan还是做出了一个与今天的文章相关的特殊设计决定——我认为这个决定可以被定性为一个错误。这是一件小事。一种微妙的东西。你可能用了好几年,甚至都没注意到它。但这很重要,因为这个错误出现在我们现在认为是“好的部分”的语言方面。

它和变量有关。

问题1:块{}不是作用域

这条规则听起来很无害:在JS函数中声明的var的作用域就是该函数的整个函数体。但这有两种让人抱怨的后果。

一、在块中声明的变量的作用域不仅仅是块本身。它是整个函数。

你可能从来没有注意到这一点。恐怕这是你无法忘记的事情之一。让我们来看看一个场景,它会导致一个棘手的错误。假设你有一些使用名为t的变量的现有代码:

function runTowerExperiment(tower, startTime) { var t = startTime; tower.on("tick", function () { ... code that uses t ... }); ... more code ... }

到目前为止,一切都很好。现在你想要添加保龄球速度测量值,因此你向内部回调函数添加了一个小小的if语句。

function runTowerExperiment(tower, startTime) { var t = startTime; tower.on("tick", function () { ... code that uses t ... if (bowlingBall.altitude() <= 0) { var t = readTachymeter(); ... } }); ... more code ... }

你无意中添加了第二个名为t的变量。现在,在“使用t的代码”中(之前运行良好),t指向新的内部变量t,而不是现有的外部变量。

JavaScript中的var的作用域就像Photoshop中的油漆桶工具。它从声明开始,在两个方向上扩展,向前和向后,一直扩展到函数边界({或})。由于变量t的作用域向后扩展了这么多,所以必须在我们一进入函数时就创建它。这叫做变量提升(hoisting)。我喜欢想象JS引擎用一个小小的代码起重机将每个var和function提升到外围函数的顶部。

变量提升有它的优点。如果没有它,许多在全局作用域中工作良好的完美的cromulent技术将无法在IIFE(立即执行函数)中工作。但是在上面的代码中,变量提升会导致一个严重的错误:使用t的所有计算将开始产生NaN。它也很难跟踪,特别是如果你的代码比这个demo更大。

但与第二个var问题相比,这是小菜一碟。

问题2:循环中的变量过度共享

你可以猜到运行这段代码时会发生什么。很简单:

var messages = ["Hi!", "I'm a web page!", "alert() is fun!"]; for (var i = 0; i < messages.length; i++) { alert(messages[i]); }

运行这段代码,浏览器会顺序弹出3次alert框,消息内容分别为"Hi!", "I'm a web page!", "alert() is fun!"。现在我们把代码稍微改动一下:

var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"]; for (var i = 0; i < messages.length; i++) { setTimeout(function () { console.log(messages[i]); }, i * 1500); }

再次运行发现,结果出乎预料。浏览器没有按顺序说出打印三条信息,而是打印了三次undefined。你能发现漏洞吗?

这里的问题是只有一个变量i。它由循环本身和所有三个setTimeout回调函数共享。当循环运行结束时,i的值为3(因为messages.length为3),并且此时还没有调用任何回调函数。(异步,事件循环)

因此,当第一个setTimeout回调函数触发并调用console.log(messages[i])时,它使用的是messages[3](messages[3]肯定是undefined)

有很多种解决的方法,下面是一种:

var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"]; for (var i = 0; i < messages.length; i++) { setTimeout((function (index) { return function() {console.log(messages[index])}; })(i), i * 1500); }

如果一开始就没有这种问题,那就太好了。

let, const是新的var

在大多数情况下,JavaScript(也包括其他编程语言,尤其是JavaScript)中的设计错误是无法修复的。向后兼容性意味着永远不会改变Web上现有JS代码的行为。即使是标准委员会也没有能力,比如说,解决JavaScript自动分号插入的奇怪问题。浏览器制造商不会实现破坏性的更改,因为这种更改会惩罚用户。大约十年前,当Brendan Eich决定解决这个问题时,只有一种方法。

他添加了一个新的关键字let,可以用来声明变量,就像var一样,但是有更好的作用域规则。

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

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

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