var,let和const深入解析(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 接着介绍var与let,const在提升的区别,还有随之而来的临时死区和双定义的问题的探讨

你想在在变量声明之前就使用变量?以后再也别这样做了。

新的声明方式(let,const)较之之前的声明方式(var),还有一个区别,就是新的方式不允许在变量声明之前就使用该变量,但是var是可以得。请看下面的代码,下面这个代码是可以正常运行的:

function func() {
  console.log(localVariable);   // undefined
  var localVariable = 5;

  console.log(localVariable);   // 5
}

func();

但是这种却不可以

function func() {
  console.log(localVariable); // ReferenceError: localVariable is not defined
  let localVariable = 10;

  console.log(localVariable); // 10
}

func();

等下,我们上一章曾经介绍了一个叫“提升”的概念,它会吧所有的变量定义在作用域的最前面。这是否意味着如果我不在实际的定义之前使用变量,然后就不会有提升了呢?答案是否定的。提升依然会有,并且适用于所有类型的变量类型。但是const和let却不是这样的。

首先,我们看一下var关键字是怎么工作的。规范对其是这样进行的描述的。

var声明定义了在正在运行的执行上下文(running execution context)作用域内的变量环境(VariableEnvironment中)的变量。var变量在当包含的词法环境(Lexical Environment)初始化时被创建,在创建的时候被赋值为undefined。[...] 在执行VariableDeclaration时,由带有Initializer的VariableDeclaration定义的变量被赋其设定项的Initializer's AssignmentExpression的值。

规范中有许多的细节,让我们简单的来看一下:

当你进入到一个作用域中,在内部被定义的所有的变量都会被创建。

所有存在的变量,都可以被访问,并且会把undefined赋值给该变量。

当代码(执行时)到达初始化时,会被分配给一个实际的值。

我们来看一下规范中对let和const的表述:

let和const声明是定义在当前执行上下文作用域中的词法环境中的变量。当包含的词法环境被初始化的时候,变量被创建。但是在变量的词法绑定时被计算之前是不允许通过任何方式来访问的。当词法绑定计算时而不是在变量被创建的时候,由词法绑定定义的变量的初始值被被赋予赋值表达式的值(也就是“=”右边的表达式)。当词法绑定被计算的时候,如果let声明中没有初始化的值的时候(也就是“let a;”这样的形式),会被赋值为undefined。

简单来说:

如果你进入到了指定的作用域中,它里面定义的所有的变量都会被初始化,这一点和var很像。

这里有一个不同点:像var一样,所有的变量都会存在,但是他们目前还不能被访问(里面没有值,甚至是undefined)。

如果let变量在相同的地方被定义和初始化,他们会被赋予合适的值,反之,变量就是undefined。const变量必须在定义的时候初始化。

我们来看一些相关的例子。

临时死区

实际上,这种描述引出了我们的另一个定义。他很让人可怕,因为他叫:临时死区(TDZ)。这个属于明确了一个我们无法访问我们的变量的代码的区域。我们来看一下下面的代码和相关联的注释,来简单的解释一下TDZ是什么。

function func() {
  // Start of TDZ for deadVariable
  // we can still do something here, just our deadVariable is not available yet
  const exampleVariable = 5;
  console.log(exampleVariable); // 5
  // End of TDZ for deadVariable
  let deadVariable = 10;

  console.log(deadVariable);  // 10
}

func();

有一件事情值得去提醒。就是对于名字的建议,这是一个临时死区,意思这个区域是由时间定义的,而不是位置。因此当运行代码的时候,你的声明在被JS解析器解析之前是不能被访问的。因此你把使用的变量的位置放在哪里并不重要,只要是在声明执行后访问该变量就可以。所以看下面的代码:

function func() {
  return deadOrAlive;
}

let deadOrAlive = 'alive!'
console.log(func());  // alive!

这是运行代码的步骤:

函数被声明

变量deadOrAlive被声明,并且初始化了一个值“alive”

现在我们调用我们的函数。

由于变量deadOrAlive已经被声明,是可访问的,因此会打印出正确的结果 “alive”。

但是下面的例子却会报错,思考一下原因。

function func() {
  return deadOrAlive;
}

console.log(func());  // ReferenceError: deadOrAlive is not defined
let deadOrAlive = 'dead!'

所以TDZ是一个避免因先使用后声明而导致的一些诡异的bug而出现的一个很好机制(具体看“提升”相关内容)。我们不需要去额外做什么事情,就是记住永远不要在变量声明之前使用这个变量。即使我们这样做了,我们也会得到一个很好的报错信息。只有一个条件-你必须使用let或者是const来替换掉var。

双定义

var和let,const的另一个区别是 - 后者仅仅可以被定义一次。而对于var的话,如果被同时定义多次,程序也依然会很好的运行。

var doubledVariable = 5;
var doubledVariable = 6;

console.log(doubledVariable); // 6

但是现在,当你用let和const来做同样的事情,就会得到一个语法错误:

let doubledVariable = 5;
let doubledVariable = 6;  // SyntaxError: Identifier 'doubledVariable' has already been declared

但是,在嵌套的块级作用域中,使用相同名字的变量依然会很好的工作的,这个我想大家已经清楚了,就不用过多解释了吧。

let doubledVariable = 5;

if (true) {
  let doubledVariable = 6;
  console.log(doubledVariable); // 6
}

console.log(doubledVariable); // 5

不能重复定义这个功能实际上是很有用的,可以组织很多bug的发生。比如说你曾经在一个函数内,在不同地方用var定义了多个相同名称的变量,此时之前定义的变量可能会被覆盖,这样对于代码来说无疑是一个隐患,也就是因为这样,这个特性实际上是一个简单的,开箱即用的解决方案。

总结

总结一下,在ES6中有两种新方法来声明变量:通过let和const关键字,除此之外,两者都是块级作用域,并且在声明之前不能访问该变量。与之前的var相比是一个主要的升级。并且会消除你很多的困扰。我提出了几个例子,可能会帮助你节省了不少调试的时间,但是还有更多。如果你感兴趣的话,可以在网上简单的搜索一下。很久之前,我个人曾建议停止使用var关键字,所以现在我的代码里充满了let和const。我建议你也是这样,在以后当你想改变变量的值,就使用let和const。不要再使用var了。

本文翻译自:

https://blog.pragmatists.com/let-your-javascript-variables-be-constant-1633e56a948d

转载自:http://www.lht.ren/article/16/

目录
相关文章
|
8月前
|
JavaScript 前端开发 C++
|
7月前
|
JavaScript 前端开发 开发者
JavaScript中的const关键字解析
JavaScript中的const关键字解析
|
7月前
|
SQL 自然语言处理 前端开发
【JavaScript】ECMAS6(ES6)新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
【JavaScript】ECMAS6(ES6)新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
55 2
|
7月前
|
SQL 自然语言处理 JavaScript
【JavaScript】ECMAS6(ES6)新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
ES6,作为ECMAScript 2015的简称,标志着JavaScript编程语言的一个重要进化节点。它不是渐进的变化,而是一次飞跃式的更新,为开发者带来了一系列强大的新特性与语法糖,极大提升了代码的简洁性、可读性和运行效率。从新的变量声明方式let与const,到优雅的箭头函数、模板字符串,再到让对象操作更为灵活的解构赋值与增强的对象字面量,ES6的每项改进都旨在让JavaScript适应日益复杂的应用场景,同时保持其作为脚本语言的活力与魅力。本文是深入探索这些核心特性的起点,为你铺开一条通向高效、现代JavaScript编程实践
76 0
|
8月前
const的定义和使用解析
const的定义和使用解析
|
8月前
|
编译器 C++
C++ 中 const 和 constexpr 关键字解析:常量、函数和指针
很多 C++ 的初学者看到 const 这个关键字的第一反应都是一头雾水,主要是因为 const可 以出现在很多的位置,以及后面加入的 constexpr 更是常常感到困惑,今天就为大家一一解释出现它们的含义和以及作用
210 0
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
93 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
89 0
|
15天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
15天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多