【ECMA学JS】深入let、const、var变量声明特性、声明提升与暂时性死区

本文深入探讨了JavaScript中let、const和var声明变量的差异,包括暂时性死区(TDZ)、重复声明、初始化和赋值等概念。通过分析ES6规范,解释了let和const如何在执行上下文中创建和初始化,以及与var声明的不同之处,帮助开发者更好地理解变量声明的内部机制。
摘要由CSDN通过智能技术生成

let、const、var是JS中的声明关键字

let和const

我们熟知的let和const的特性,常见的就有以下四点:

1.let和const声明的变量在未初始化之前不可以被使用。(暂时性死区TDZ)

2.let和const声明的变量,在同一个执行上下文中不可以被重复声明。

3.let可以只声明,后面再赋值,未赋值的话初始化值为undefined。

4.const一经声明必须马上初始化。

var

1.可以在声明之前访问
2.可以在同一个执行上下文中声明重复的变量。

但是如果问我,let和const声明的变量为什么会这样,为什么跟var声明的变量不一样,一时间我又说不出个所以然。所以就去研究了一下ECMA的文档。

Let and Const DeclarationsES6原文定义:let和const声明的变量会挂载到执行上下文的词法环境当中去,这些变量会在包含它们的环境记录项初始化的时候被创建。但是在变量的词法绑定执行之前他们是无法被访问的。通过词法绑定定义的变量,如果包含初始化语句,那么词法绑定执行的时候,赋值操作也会执行,而不是在变量创建的时候赋值。
let语句,允许变量在词法绑定的时候不同时做初始化操作,它会在词法绑定的时候初始化为undefined。

从这个定义当中,我们可以粗略提取出我们了解过的let和const的特性。
但是每个特性具体是怎么实现的,还要根据其声明的具体实现来看。

 Let and Const Declarations Syntax

从let和const声明的静态语义语法来看,它分为两大阶段:

  • LexicalDeclaration词法声明
    错误检查阶段:
    在词法声明阶段会检查let不能作为变量关键字,以及绑定列表中不允许出现重复的标识符。(这就是特性2)
    标识符绑定阶段:
    把标识符名称添加到绑定的标识符列表中(BindingList)
  • LexicalBinding词法绑定
    错误检查阶段:
    词法绑定阶段会去判断const声明变量是否带有初始化。(特性4 get)
    标识符初始化阶段:
    执行声明的时候自带初始化器的标识符的初始化。(也可以理解为把执行赋值操作)没有初始化的let声明变量赋值为undefined(特性3 get)
    所以我们可以看到let声明过程中,它的标识符变量创建和初始化与赋值是分开的

但是到目前为止,let和const暂时性死区这个特性其实理解起来还是没有那么直观。

为了有更清楚的对比,我们先看var变量声明的过程中发生了什么。

VariableStatementvar声明的变量会挂载到执行上下文的变量环境当中。当变量包含的环境记录项在创建的时候即会被实例化和初始化为undefined。
当var声明的变量,如果带有赋值语句,赋值操作会在代码执行的时候完成,而不会在变量一声明创建的时候就执行。

所以看完var和let的声明语义之后,我们可以总结出来:
变量声明其实分为三个步骤:

1.变量标识符的创建

2.变量标识符的初始化

3.变量标识符的赋值

var的处理方式是,标识符创建的时候,不管你有没有赋值语句,它先把变量初始化为undefined。
如果var语句后面跟了赋值语句,在创建完标识符之后,代码执行阶段再把变量标识符的值更新成赋值的内容。
可以说var声明的变量,不管
例如var a = '123';这个语句可以拆解成var a; a='123';,在声明解析变量标识符绑定阶段,JS只去解析了var a声明,因为var声明的特性,此时a在变量环境中被创建,并且直接初始化为undefined了。
但是只有当JS到了执行阶段,才会去执行a=‘123’;此时变量环境中的a取值才会被更新为’123’;
这就是为什么以下代码能执行,并且顺序不同打印的结果不同。

console.log(a);//undefined
var a = '123';
var  b;
console.log(b);//undefined
console.log(a);//123
b = 2;
console.log(b);//2

这个的执行可以抽象成这样:

var a;//变量标识符声明创建阶段,此时a没赋值不能访问
var b;//变量标识符声明创建阶段,此时b没赋值不能访问
a = undefined;//变量标识符声明创建阶段,默认赋值可以访问了
b = undefined;
console.log(a);
a = '123';
console.log(b);
console.log(a);
b = 2;
console.log(b);

那么再说回到let,let声明的过程也是有创建、初始化、赋值三部曲。
但是问题是,它和var的区别就在于,let标识符创建的时候就只创建了变量,标识符名称绑定了,但是初始化是等到赋值阶段再初始化,也就是说如果let声明的变量带了赋值内容,就不在初始化了,没有才会初始化为undefined。
所以当执行的时候,let不能在它声明之前使用。

console.log(p);//Uncaught ReferenceError: p is not defined
let p;
{
  console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization
  var c = 2;
  let x = 1
}
console.log(c);//2
console.log(x);//Uncaught ReferenceError: x is not defined

上面这个例子当中,{}块语句产生了新的块作用域。
let声明的变量是绑定到{}块作用域里面的,所以在块作用域之外要访问x会报错,但是 var声明的变量是挂载到当前的函数作用域里面的,所以可以访问。

Advanced JavaScript ES6 — Temporal Dead Zone, Default Parameters And Let vs Var — Deep dive!

The Difference Between Function and Block Scope in JavaScript

JavaScript ReferenceError – Can’t access lexical declaration`variable’ before initialization

总结:变量提升和暂时性死区(TDZ)

  • 变量提升:代码顺序上,变量调用在前,声明在后,可以调用该对象。(var)
  • 暂时性死区(TDZ):变量在初始化之前不可引用,否则会报错。(let,const)
function test(){
  console.log(a);
  let a;
}
test();//报错:Cannot access 'a' before initialization

function test2(){
  let b;
  console.log(b);
}
test();//undefined

console.log(c);// Uncaught ReferenceError: c is not defined
let c;

console.log(b);//
var b;
console.log(b);
b = 1;

在执行上下文创建的时候,letconst定义的变量的值是没有初始化的,但是var定义的变量的值会被直接初始化为 undefined

在执行 fn 时,会有以下过程(不完全):

  • 进入 fn,为 fn 创建一个环境。
  • 找到 fn 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
  • 将这些变量「初始化」为 undefined。
  • 开始执行代码
  • x = 1 将 x 变量「赋值」为 1
  • y = 2 将 y 变量「赋值」为 2

所以,var声明的变量在一开始就挂载到了词法环境当中,并且对应的标识符默认被赋值为undefined,也就是说var声明的对象,一开始就完成了完整的初始化。

而let声明的变量,虽然一开始标识符也挂载到词法环境当中了,但是标识符没有有赋值,还处在未初始化的状态,所以,let在初始化前是不能被访问的,从代码顺序上也就是在声明之前不能被访问。

变量提升Hoisting

let是否存在提升

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值