资讯专栏INFORMATION COLUMN

深入ES6:let和const

SKYZACK / 446人阅读

摘要:是一系列关于在标准的第六版中加入编程语言的新功能,简称。因此,虽然警报可见,但输入事件不会传递。即使标准委员会也没有权力,比如用的自动分号插入修正奇怪的怪癖。这个变量在范围内但未初始化的时期称为时间盲区。标准委员会考虑使用这种范围规则来让。

ES6 In Depth是一系列关于在ECMAScript标准的第六版中加入JavaScript编程语言的新功能,简称ES6。

我今天想谈的这个特点既简单又令人感到惊喜。

当Brendan Eich在1995年设计了JavaScript的第一个版本时,其中有很多问题,包括自此以后一直是该语言的一部分的东西,比如Date对象和对象在意外地相乘时会自动转换为NaN。然而,事后看来,他所做的事情是非常重要的事情:_objects_; _prototypes_;_function_和_作用域_;可变数据类型。这门语言有很好的结构。比任何人一开始意识到的要好。

尽管如此,Brendan 还是做了一个特别的设计决定 - 这个决定我认为是一个错误。这是一件小事。一个微妙的东西。您可能会使用该语言多年,甚至没有注意到它。但它很重要,因为这个错误在我们现在认为是“好的部分”的语言中。

它与变量有关

问题#1:块不是范围

规则听起来很合理: JS函数中声明的var的作用域是该函数的整个主体。 但是有两种方式会导致不同的后果。

一个是块中声明的变量范围不仅仅是块。是整个函数。

你以前可能从未注意到这一点。恐怕这是你无法看不到的东西之一。让我们通过一个会导致棘手问题的场景来说明这个bug。

假设您有一段代码是使用名为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中的绘画工具。它从声明的两个方向延伸,向前和向后,并且它继续前进,直到它到达函数边界。由于这个变量的范围向后延伸,所以只要我们进入函数就必须创建它。这被称为提升。我喜欢想象JS引擎将每个变量和函数用一个小代码起重机提升到封闭函数的顶部。

现在,变量提升有其好处。没有它,很多在全局范围内工作得很好的完美适用的技术在 IIFE内部不起作用。但是在这种情况下,提升造成了一个令人讨厌的bug:所有使用t的计算都将开始生成NaN。这也很难追查,特别是如果你的代码比这个例子更大的时候。

添加一个新的代码块导致该块之前的代码中出现神秘错误。我们不希望效果先于原因。

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

问题#2:循环中的变量泛滥

你可以猜测当你运行这段代码时会发生什么。这非常简单:

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

如果你一直在关注这个系列,你就知道我喜欢用alert()作为代码。也许你也知道alert()是一个糟糕的API。它是同步的。因此,虽然警报可见,但输入事件不会传递。您的JS代码 - 实际上是您的整个UI - 基本上都会暂停,直到用户单击确定。(译者: alert具有阻塞性)

所有这些都让alert()几乎成为了你想要在网页中做的任何事情的错误选择。我使用它是因为我认为所有这些相同的东西都使alert()成为一个很好的教学工具。

尽管如此,我还是可以说服自己放弃所有这些笨拙和不良行为......如果这意味着我可以做一个说话的猫。

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

看到这个代码工作不正确!

B但有些事情是错的。猫没有按顺序说出所有三条消息,而是三次说“undefined”。

你能发现错误吗?

这里的问题是,我只有一个变量。它由循环本身和所有三个超时回调共享。当循环结束运行时,i的值为3(因为messages.length为3),并且还没有调用任何回调。(译者:可以去了解一下Event loop)

所以当第一次超时触发并调用cat.say(messages [i])时,它使用消息[3]。那个当然是undefined的。

有很多方法可以解决这个问题(这里是一个),这是由var范围规则引起的第二个问题。

let 是新的 var

大多数情况下,JavaScript中的设计错误(其他编程语言,特别是JavaScript)也无法修复。向后兼容意味着永远不要改变Web上现有JS代码的行为。即使标准委员会也没有权力,比如用JavaScript的自动分号插入修正奇怪的怪癖。浏览器制造商根本不会实施突破性的改变,因为这种改变惩罚了用户。

大约十年前,当Brendan Eich决定解决这个问题时,实际上只有一种方法可以解决这个问题。

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

它看起来像这样:

let t = readTachymeter();

或则这样:

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

let和var是不同的,所以如果你只是在整个代码中进行全局搜索和替换,那可能会破坏你的代码的一部分(可能无意)依赖于var的怪癖。但绝大多数情况下,在新的ES6代码中,您应该停止使用var并使用let来代替。因此口号是:“let is the new var”。

let和var之间究竟有什么区别?很高兴你有这样的疑问!

let 变量是 block-scope. 用let声明的变量的作用域就是封闭块,而不是整个封闭函数。

runTowerExperiment示例可以通过将var更改为let来修复。如果你在任何地方使用let,你将永远不会有这样的错误。

全局let变量不是全局对象的属性。也就是说,你不会通过编写window.variableName来访问它们。相反,他们生活在一个隐形块的范围内,这个块在概念上包含了所有在网页中运行的JS代码。

(let x ...)形式的循环在每次迭代中为x创建一个新的绑定。.

这是一个非常微妙的差异。这意味着如果一个for(let ...)循环执行多次,并且该循环包含一个闭包,就像我们在谈论猫的例子中一样,每个闭包将捕获循环变量的不同副本,而不是捕获相同的闭包循环变量。

所以说话的猫例子也可以通过改变var来解决。 这适用于所有三种for循环:for-of, for-in,以及带分号的旧式C类。

在声明之前尝试使用let变量是错误的。 在控制流到达声明的代码行之前,该变量是未初始化的。例如:

 function update() {
 console.log("current time:", t);
 // ReferenceError
 ...
 let t = readTachymeter();
 }
 

这条规则可以帮助你发现错误。代替NaN结果,您会在问题所在的代码行中发现异常。

这个变量在范围内但未初始化的时期称为时间盲区。

(脆弱的性能细节:在大多数情况下,通过查看代码可以判断声明是否运行,所以JavaScript引擎在每次访问变量时都不需要执行额外的检查,以确保它已经但是,在一个闭包中,有时候不清楚,在这种情况下,JavaScript引擎会执行一次运行时检查,这意味着可以比var慢一点。)

(脆弱的作用域范围详细说明:在一些编程语言中,变量的范围从声明开始,而不是向后覆盖整个封闭块。标准委员会考虑使用这种范围规则来让。这样,引用ReferenceError的t的使用根本就不在后面的let t的范围内,所以它根本不会引用该变量,它可以引用在封闭范围内的at。方法在封闭或功能提升方面效果不佳,因此最终放弃了)。

用let重复声明一个变量是一个SyntaxError(译者:语法错误)。

这条规则也可以帮助你发现微不足道的错误。尽管如此,如果尝试进行全局let-to-var转换,最有可能导致您遇到一些问题的区别,因为它甚至适用于全局let变量。

如果你有几个脚本都声明了相同的全局变量,那么最好继续使用var。如果您切换到let,无论哪个脚本加载第二个将失败并出现错误。

或者使用ES6模块。但那又是别的故事。

(Crunchy语法细节:let是严格模式代码中的保留字,在非严格模式代码中,为了向后兼容,您仍然可以声明名为let的变量,函数和参数,您可以编写var let =" q")

除了这些差异之外,let和var几乎是一样的。
例如,声明多个由逗号分隔的变量,并且它们都支持解构

请注意,class声明的行为如let,而不是var。如果多次加载包含类的脚本,则第二次重新声明该类时会出现错误。

const(常量)

对,还有一件事!

ES6还引入了第三个关键字,您可以使用let:const。

用const声明的变量就像let,除了你不能指定给它们,除了它们被声明的地方。这是一个SyntaxError。

const MAX_CAT_SIZE_KG = 3000;
//            
               
                                           
                       
                 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94907.html

相关文章

  • 深入理解ES6笔记(一)块级作用域绑定

    摘要:和都能够声明块级作用域,用法和是类似的,的特点是不会变量提升,而是被锁在当前块中。声明常量,一旦声明,不可更改,而且常量必须初始化赋值。临时死区临时死区的意思是在当前作用域的块内,在声明变量前的区域叫做临时死区。 主要知识点有:var变量提升、let声明、const声明、let和const的比较、块级绑定的应用场景showImg(https://segmentfault.com/img...

    马忠志 评论0 收藏0
  • 深入理解:ES6中的SetMap数据结构,Map与其它数据结构的互相转换

    摘要:学习笔记工作中常用到的语法只是简单提及和,今天有空于是写了这篇文章深入理解中的和数据结构,与其它数据结构的互相转换。的提供了新的数据结构。本身是一个构造函数,用来生成数据结构。 文中的内容主要是来自于阮一峰的《ES6标准入门》(第三版)。《学习ES6笔记──工作中常用到的ES6语法》只是简单提及Set和Map,今天有空于是写了这篇文章──《深入理解:ES6中的Set和Map数据结构,M...

    Cristalven 评论0 收藏0
  • 深入理解ES6 (一) 块级绑定

    摘要:声明声明的语法与的语法一致。总结文章都是以深入理解读书笔记形式,大部分引用书中的定义,加上作者的理解,样例也做了调整,所有样例都可以放到里运行亲自尝试。 1.变量提升 使用 var 关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的 顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。这句话从字面上不难理解。 但是他是怎样一个过程,为什么会这样。当你代...

    KunMinX 评论0 收藏0
  • 深入理解ES6》笔记——块级作用域绑定(1)

    摘要:没有声明的情况和都能够声明块级作用域,用法和是类似的,的特点是不会变量提升,而是被锁在当前块中。声明常量,一旦声明,不可更改,而且常量必须初始化赋值。临时死区的意思是在当前作用域的块内,在声明变量前的区域叫做临时死区。 本章涉及3个知识点,var、let、const,现在让我们了解3个关键字的特性和使用方法。 var JavaScript中,我们通常说的作用域是函数作用域,使用var声...

    2bdenny 评论0 收藏0
  • 深入理解ES6》笔记——SymbolSymbol属性(6)

    摘要:设置对象属性只读。提供了一个注册机制,当你注册之后,就能在全局共享注册表里面的。的注册表和对象表很像,都是结构,只不过这个是值。语法只有一个参数,返回的是从注册表获取全局共享的注意如果要防止命名重复问题,可以加上前缀。 还记得对象Object吗? let obj = { a: 1 } 对象的格式: Object { key: value } 在ES5的时代,对象的key只能...

    heartFollower 评论0 收藏0

发表评论

0条评论

SKYZACK

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<