美文网首页前端开发那些事儿程序员
【JS】从ECMA学习let、const、var变量声明与声明提

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

作者: 匿于烟火中 | 来源:发表于2020-12-17 11:16 被阅读0次

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

    相关文章

      网友评论

        本文标题:【JS】从ECMA学习let、const、var变量声明与声明提

        本文链接:https://www.haomeiwen.com/subject/pzzvgktx.html