65.9K
CodeProject 正在变化。 阅读更多。
Home

ES6 中块级作用域的乐趣

2016年6月16日

CPOL

9分钟阅读

viewsIcon

21644

本文将解释 `const` 和 `let` 为何有用以及如何使用它们。

布兰登·艾奇 (Brendan Eich) 大约在 1995 年 5 月 6 日至 15 日,仅用 10 天就发明了 JavaScript。这种语言最初名为 Mocha,最初是一种简单的客户端脚本语言。O'Reilly 关于该语言的历史详细介绍了该语言发明时的背景。

艾奇最终认为,一种松散类型的脚本语言适合当时的环境和受众,即当时数千名需要能够与页面元素(如表单、框架或图像)关联,而无需字节码编译器或面向对象软件设计知识的网页设计师和开发人员。

JavaScript 诞生时,网络与现在截然不同,但它最终将成为最普遍使用的编程语言之一。很少有人能预测它会变得多么流行。因此,JavaScript 自然地存在一些设计缺陷,这些缺陷自其诞生以来一直困扰和迷惑着开发人员。

常见的抱怨之一是 JavaScript 缺乏块级作用域。与其他流行的语言(如 CJava)不同,JavaScript(ES6 之前)中的块 ({...}) 没有作用域。JavaScript 中的变量作用域限定在其最近的父函数,如果没有函数,则为全局作用域

当被问及 JavaScript 为何没有块级作用域时,布兰登·艾奇的回答非常直接:没有足够的时间添加块级作用域。

幸运的是,JavaScript 源于 C/C++/Java,并且为将来添加块级作用域做好了准备,正如 艾奇在这一系列推文中解释的那样

现在,ECMAScript 6 (ES6) 终于问世了,并且在许多方面,它通过两个新的 ES6 变量关键字 `let` 和 `const` 为我们提供了块级作用域。对 `let`/`const` 的支持仍然仅限于 Edge、Chrome 和 Firefox,但更多浏览器可能很快会支持它。

本文将解释 `const` 和 `let` 为何有用以及如何使用它们。

来源:http://www.cs.uni.edu/~wallingf/blog/archives/monthly/2012-10.html

使用 `var` 的挑战

在介绍 `const` 和 `let` 之前,有必要先讨论一下它们为什么有用或必要。到目前为止,`var` 一直是 JavaScript 中唯一的变量关键字,但它有几个缺点。

`var` 作用域对来自其他语言的开发人员来说很混乱

`var` 的作用域对于来自其他语言的开发人员来说很混乱。他们很容易在使用了 `if` 块或 `for` 循环的代码中无意中导致 bug。ES5 及更早版本中的变量声明方式与他们预期的不同。鉴于 JavaScript 的流行,来自其他语言的开发人员有时不得不编写 JavaScript,因此,更容易理解的变量作用域对他们来说会很有帮助。

`var` 造成的全局与局部混淆

使用 `var` 编写 JS 时,很难立即辨别哪些变量是局部作用域,哪些是全局作用域。在 JavaScript 中意外地在全局对象上创建变量非常容易。这通常不会影响简单的演示应用程序,但可能会给企业级应用程序带来问题,因为团队成员会意外地覆盖彼此的变量。

令人困惑的变通模式

JavaScript 中缺乏清晰的全局与局部作用域区分,这迫使开发人员提出了像 IIFE(立即调用函数表达式)这样的模式。这是一种对缺乏块级作用域的笨拙变通方案。它也是一种避免将 `var` 声明的变量附加到全局对象的方法。

(function(){

// code here

}());

如果您不是经验丰富的 Javascript 开发人员,这种模式就没什么意义了。这里发生了什么?这些括号都是什么?为什么这个函数没有名字?块级作用域应该会减少对这种变通设计模式的需求。

关于提升的误解

`var` 的另一个挑战是它不像大多数开发人员想象的那样工作。JavaScript 解释器对一段 JavaScript 代码进行两次传递。第一次传递处理变量和函数声明并将它们提升到顶部(“提升”)。第二次传递处理函数表达式和未声明的变量。如果开发人员不清楚提升的工作原理,这会导致一些令人困惑的代码。以开发人员 Ben Cherry 的博客中的这个例子为例

本文中的截图来自 Mac 上的 Visual Studio Code

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/VvVNpg?editors=101

在上面的例子中,`var foo = 10;` 被提升到最近的父函数。这就是为什么它会警告 10 而不是预期的 1。用 `let` 重构的例子更直观。

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/VvVNpg?editors=101

使用 `let`

新的 ES6 关键字 `let` 允许开发人员在块级(最近的花括号)作用域中声明变量。如果您想查看哪些浏览器支持 `let`(和 `const`),请关注 Microsoft Edge 页面以跟踪 ES6 功能的支持情况。您也可以在此处检查您的当前浏览器是否支持 `let` 和 `const`

以下是 `let`(与 `var` 相对)在不同类型块中的一些示例

`if` 块示例

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/BoGEoa?editors=101

在上面的示例中,当变量用 `var` 声明时,它作用域限定在 IIFE 函数中。在 `if` 块内,一个单独的 `fruit` 变量用 `let` 声明,并作用域限定在 `if` 块中。

`for` 循环块示例

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/KdrYdg?editors=101

上面的 `for` 循环示例比 `if` 块更有趣。通过在初始化表达式中使用 `let`,变量 `i` 的作用域仅限于该块。通过使用 `var`,`i` 的作用域将限定在最近的函数中。该变量将在 `for` 循环块之外等于 10。这可能会导致诸如创建意外闭包等后果,例如以下示例:

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/XmyQXe?editors=101

在此示例中,代码旨在在简单列表上注册事件侦听器,并警告单击了哪个数字。相反,它将为每个列表项警告**单击了数字:5**。将事件侦听器部分包装在一个函数中是避免意外闭包的常见解决方法。

但是,如果改用 `let` 重构(一旦完全支持),它将在 `for` 循环中创建一个块级作用域,并避免意外闭包。它允许在事件侦听器回调函数中使用预期的迭代器。

代码:http://codepen.io/DevelopIntelligenceBoulder/pen/JYeVGv?editors=101

`let` 的一个注意事项是,它与 `var` 的提升方式不同。如果您尝试在声明之前使用它,将会收到引用错误。这被一位开发人员称为“暂时死区”(Temporal Dead Zone)(该术语此后广受欢迎)。

使用 `const`

与其他语言中的常量一样,`const` 通常用于在程序执行期间不需要重新赋值的值。像 API 密钥这样的字符串或像 `CANVAS_HEIGHT` 这样的数字都是 `const` 变量的用例,它们不需要重新赋值。用 `const` 声明的变量通常用大写字母书写,但这只是个人偏好。

`const` 是 ES6 中用于声明变量的另一个新关键字。`const` 在许多方面都像其他语言中的常量,但也有一些注意事项。`const` 代表对值的“常量引用”。`const` 引用的值不是不可变的(它们的属性可以更改)。这可以通过借用《Eloquent JavaScript》(一本很棒的 JS 初学者书籍)中的比喻来解释。

《Eloquent Javascript》的作者 Marijn Haverbeke 说,最好将变量视为触手而不是盒子。

它们不包含值;它们抓住它们——两个变量可以引用相同的值。程序只能访问它仍然持有的值。当你需要记住一些东西时,你会伸出触手抓住它,或者将你现有的触手重新连接到它上面。

因此,使用 `const`,您实际上可以改变变量所引用的对象的属性。您只是无法改变引用本身。通过上述比喻解释,触手不会移动或改变,但它所抓住的东西可以。以下是 `const` 实际应用的一些代码示例:

1.

const PI_VALUE = 3.141592;

2.

const APIKEY = 'aekljefj3442313kalnawef';

3.

const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // error

如果你希望一个常量是完全不可变的,请使用`object.freeze`来使属性不可变。

何时使用 `const` 与 `let`

关于何时使用 `const` 与 `let`,JavaScript 社区仍有一些争论。一些人最初建议使用 `let` 代替 `var`。另一些人现在呼吁默认使用 `const` 而不是 `let`。

JavaScript 专家雷金纳德·布雷斯韦特 (Reginald Braithwaite) 的推文已得到许多人的响应。开发人员埃里克·埃利奥特 (Eric Elliot) 详细阐述了在 `let` 之前使用 `const` 的论点

如果我不需要重新赋值,`const` 是我默认的选择,而不是 `let`,因为我希望代码中的用法尽可能清晰。

`const` 是一个信号,表明变量不会被重新赋值

`let` 是一个信号,表明变量可能会被重新赋值,例如循环中的计数器,或算法中的值交换。它还表明变量将仅在其定义的块中使用,而不是始终是整个包含函数。

`var` 现在是最弱的信号

Kyle Simpson(《你不知道的 JS》的作者)对“let 是新的 var”这种热情提出了一些反驳,认为 let 声明的隐式性质可能导致 JS 代码中的问题。此外,在某些情况下,var 仍然有用。

`let` 改善了 JS 中的作用域选项,而不是取代它。`var` 仍然是贯穿整个函数使用的变量的有用信号。同时拥有并使用两者,意味着作用域意图更容易理解、维护和强制执行。这是一个巨大的胜利!

辛普森认为 `let` 是 `var` 的伴侣,不应用于替换所有 `var` 语句。

这场辩论将如何演变,仍有待观察。

摘要

一旦得到更多浏览器的支持,`const` 和 `let` 将允许在 JavaScript 中进行块级作用域变量,从而使 IIFE 等模式变得不那么必要。来自其他语言的开发人员将更容易理解 JavaScript 作用域。开发人员 Aaron Frost 俏皮地说:“使用 LET 和 CONST 而不是 VAR 会产生一个奇怪的副作用,即您的代码在运行时会像在开发时一样执行。”

本文是 Microsoft 技术推广人员和 DevelopIntelligence 关于实用 JavaScript 学习、开源项目和互操作性最佳实践(包括 Microsoft Edge 浏览器)的 Web 开发系列文章之一。DevelopIntelligence 为技术团队和组织提供由讲师主导的 JavaScript 培训React 培训

我们鼓励您使用 dev.microsoftedge.com 上的免费工具,在各种浏览器和设备(包括 Windows 10 的默认浏览器 Microsoft Edge)上进行测试,其中包含 F12 开发人员工具——七种独特、功能齐全的工具,可帮助您调试、测试和加快网页速度。此外,请访问 Edge 博客,获取 Microsoft 开发人员和专家的最新信息。

© . All rights reserved.