【JS】从ECMA学习let、const、var变量声明与声明提升那些事

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 Declarations定义: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声明的变量是挂载到当前的函数作用域里面的,所以可以访问。
我用了两个月的时间才理解 let

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)

ES6原文:let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.
let和const声明的变量会挂载到执行上下文的词法环境当中。当变量所在的词法环境被实例化的时候,变量就被创建了,但是在变量的词法绑定(LexicalBinding)赋值之前,它并不能被访问。
A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created.
带有初始化器(Initializer)的词法绑定(LexicalBinding)所定义的变量,在LexicalBinding赋值的时候,初始化器才会把值赋给这个变量,而不是在词法绑定创建的时候赋值。
If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
如果let声明的变量,其词法绑定没有初始化,该变量在词法绑定赋值的时候会被初始化为undefined。

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是否存在提升

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容