前端系统化学习【JS篇】:(十)VAR、LET、CONST的区别、块级作用域

683 阅读8分钟

前言

  • 细阅此文章大概需要 10分钟\color{red}{10分钟}左右
  • 本篇中讲述了:
      1. JS中声明变量和函数的方式
      1. LET和CONST的区别
      1. LET和VAR的区别
          1. LET不存在变量提升
          1. LET不允许重复声明
          1. 暂时性死区
          1. LET会产生块级作用域
      1. 块级作用域的多种情况及详解
  • 如果有任何问题都可以留言给我,我看到了就会回复,如果我解决不了也可以一起探讨、学习。如果认为有任何错误都还请您不吝赐教,帮我指正,在下万分感谢。希望今后能和大家共同学习、进步。
  • 下一篇会尽快更新,已经写好的文章也会在今后随着理解加深或者加入一些图解而断断续续的进行修改。
  • 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!
  • 欢迎转载,注明出处即可。

VAR、LET、CONST的区别

JS中声明变量和函数的方式

  • 【传统】
    • var
    • 函数声明【function xxx(){}】
    • 函数表达式(字面量)【var xxx = function(){}】
  • 【ES6】
    • let
    • const
    • 函数表达式的箭头函数【let xxx = ()=>{};】
    • 模块导入【import xxx from 'xxx'】

LET和CONST的区别

  • LET创建的是 变量,只不过他的指针指向可以随意的修改。
  • CONST创建的是 变量,只不过他的指针指向一旦确定,就不能再修改。【看起来像但不是常量】
    • const创建的变量值是不允许被修改的~~【相当于java中Final修饰的变量】~~

LET和VAR的区别

  • 1. LET不存在变量提升

    • 在代码执行阶段,在let定义一个变量之前就使用它,【浏览器会先看看下面的代码中有没有let声明该变量】
      • 若声明了则会报错 【不能再let声明之前使用该变量】
      • 否则报错【未定义】

  • 2. LET不允许重复声明

    • 在同一个上下文中不允许使用LET重复声明同一个变量【只要这个变量已经存在】,且 检测报错发生在词法解析(AST词法解析树)阶段,而不是在代码执行阶段
      • (词法解析【(AST词法解析树):把代码拆成对应的字符,并且识别成为浏览器可以解析的对象】【此阶段包含作用域链、形参赋值、this指向、变量提升等等】,词法解析结束后才会进行代码执行

  • 3. 暂时性死区问题

    • 浏览器bug,检测一个未被声明的变量,不会报错,结果是"undefined"用let可以解决

  • 4. LET会产生块级作用域

  • 【ES6】块级作用域

  • 在ES6中,基于let/const/function...创建变量

  • 如果是出现在 【非函数】【非对象】 的大括号中 【大括号中出现let/const/function...都会被认为是块级作用域】 ,则这个大括号的范围内相当于一个块级作用域【在执行时会创建私有上下文】,

    • {}中存在FUNCTION这个关键词会产生块级作用域也是新版浏览器才加入的
  • 不止是在IF中,新版浏览器为了保证语义的准确性,如果函数出现在【除函数/对象】的大括号{}中,形成了块级作用域,则在【全局上下文中在变量提升阶段,对于此FUNCTION只声明不定义】

  • 即使在大括号中同时出现LET\CONST\FUNCTION 和VAR,虽然会产生块级作用域,但是对VAR不生效

    • 【在块级作用域中VAR声明定义的变量会影响上级上下文,(上级上下文已存在的【被function/var声明 定义的】变量会被修改,不存在的则会被同步创建一个)】
    	/* 循环中的块级上下文 */
        for(let i = 0; i<5;i++){
            console.log(i);
        }
        //此时For循环的大括号就是一个块级作用域,
        //这个for就相当于拥有自己的私有上下文
        //当循环结束,一共产生了六个私有上下文/块级作用域
        //for{}由于有let,所以本身成为了一个,然后循环了五次每次又各创建了一个
    
  • 因为【新版本浏览器】要兼容ES3/ES6,在【遇到if(块级作用域)】中存在function时会形成块级作用域【再执行函数本身时依然会有自己的私有上下文】

    1. 在变量提升阶段,在EC(G)当中,只声明了该function a,【没有定义创建堆内存】【因为是新版本浏览器对于在if中的function a只声明不定义,而在【老版本浏览器】中在EC(G)下对函数声明+定义】

    2. 而在代码执行时,进入到块级作用域的私有上下文时,也会进行变量提升,此时会对function a声明加定义【创建堆内存并与变量a关联】

    3. 而在这个块级作用域的私有上下文的代码执行阶段,则不会在处理此行代码【做过的事情不会重复做】

    4. 【重点】【只是在全局下的块级作用域,再多一层就没有映射了,...】:

    • 但是浏览器会把在这个块级作用域的私有上下文中【===此行代码之前的【包含此行代码】===】 所有对变量a的操作,映射给全局一份,以此来兼容ES3,而【===此行代码之后的【不包含此行代码】===】所有对变量a的操作,就只是对块级作用域的私有上下文中的a的操作了
          var a = 0;
          if(true){
              a = 1;
              function a(){};
              a = 21;
              console.log(a);// 21
          }
          console.log(a);// 1
          // 块级作用域的私有上下文变量提升时映射给全局,全局a = 函数;
          // 接着私有上下文代码执行,a = 1;
          // 在function之前对a的所有操作,映射给全局一份,于是此时全局a = 1;
          // 接着私有a等于函数,私有上下文变量提升阶段搞过了不再重复,于是对全局a的影响到此为止,不会再映射。
          // 接下来对a的操作都只是对私有的a进行操作
    

       {
           function foo(){};
           foo = 1;
       }
       console.log(foo); //=> function foo
       // 全局上下文中仅声明了foo
       // 真正使全局foo = 函数的,是在块级作用域的私有上下文中,变量提升时,对私有foo声明加定义,映射给全局的
       // 而之后的foo = 1;完全时对私有进行操作,和全局再也没有关系了。
    

        {
            console.log(foo);//函数foo【私有已变量提升】
            console.log(window.foo);//undefined
            //【直到在代码执行时运行到最后一次function foo结束,才会将之前的操作映射给全局】,【代码执行到此时,还没进行映射】
            function foo(){};
            foo = 1;
            function foo(){};// 执行完这句,后面的才不会映射给全局
            console.log(foo);// 1
        }
        console.log(foo);// 1
        //全局上下文中仅声明了foo
        //真正使全局foo = 函数的,是在块级作用域的私有上下文中,变量提升时,对私有foo声明加定义,(所以处理后的在执行时不会再处理)
        //映射给全局的【按照在私有上下文的变量提升中function foo的最后一次算】
        //而之后私有上下文代码执行foo = 1;在function语句之前,映射给全局一份,全局a = 1;
        //于是对全局a的影响到此为止,之后完全时对私有进行操作,和全局再也没有关系了。
    

        var a = 12;//函数a【划掉】//13【最终】
        if(true){
            console.log(a);//函数a //块级作用域私有上下文变量提升,函数声明+定义
            a = 13;
            console.log(a);//13
            function a(){};//之前映射给全局
            a = 14;
            console.log(a);//14
        }
        console.log(a);//13【全局】
        
    

  • 进阶

        //EC(G)
        //变量提升
        //VAR X
        //FUNC = 函数
        var x = 1;
        function func (x,y = function anonymous1(){x = 2}){
            //EC(func)私有
            //作用域链<EC(func),EC(G)>
            //形参赋值x=5 y = 函数anonymous1【作用域EC(func)】
            //变量提升--
            x = 3;
            y();
                //EC(anonymous1)私有
                //作用域链<EC(anonymous1),EC(func)>
                //形参赋值--
                //变量提升--
                //x用的是上级上下文的
            console.log(x);// 2
        }
        func(5);
        console.log(x);// 1
    

    	//【函数体的大括号形成块级上下文的情况】
        //EC(G)
        //变量提升
        //VAR X
        //FUNC = 函数
        var x = 1;
        function func(x,y =function anonymous1(){x = 2}){
            //EC(func)私有
            //作用域链<EC(func),EC(G)>
            //形参赋值x=5 y = 函数anonymous1【作用域EC(func)】
            //变量提升--
                
            //=================================
            // 发现满足函数大括号形成块级上下文的条件(当中有var)
            //单独多形成一个私有的块级上下文(把函数体{}当作新的块级上下文)
            //EC(Block)私有
            //块级上下文的作用域链<EC(BLOCK),EC(FUNC)>
            //变量提升 var x  会把函数私有上下文中的形参赋值的值传递进来,赋值给声明的私有变量x = 5
            var x = 3;//EC(Block)把块级作用域中的x改为3
            y();//EC(Block)中没有私有变量y,向上查找执行EC(Func)中的y
                //EC(anonymous1)私有
                //作用域链<EC(anonymous1),EC(func)>
                //形参赋值--
                //变量提升--
                //x用的是上级上下文EC(func)的
            console.log(x);//3 =>但此处输出是在块级上下文中执行,输出的是块级上下文中的x
        }
        func(5);
        console.log(x);// 1
    

    //【函数体的大括号形成块级上下文的情况】
        //EC(G)
        //变量提升
        //VAR X
        //FUNC = 函数
        var x = 1;
        function func(x,y =function anonymous1(){x = 2}){
            //EC(func)私有
            //作用域链<EC(func),EC(G)>
            //形参赋值x=5 y = 函数anonymous1【作用域EC(func)】
            //变量提升--
            
            //=================================
            // 发现满足函数大括号形成块级上下文的条件
            //单独多形成一个私有的块级上下文(把函数体{}当作新的块级上下文)
            //EC(Block)私有
            //块级上下文的作用域链<EC(BLOCK),EC(FUNC)>
            //变量提升 var x var y  
            //形参赋值 x = 5 y =函数anonymous1() 把函数私有上下文中的形参赋值的值传递进来,赋值给声明的私有变量
            var x = 3;//EC(Block)把块级作用域中的x改为3
            var y = function anonymous2(){x = 4};//EC(Block)把块级作用域中的y改为函数anonymous2
            y();//执行EC(Block)中y指向的anonymous2,
                //EC(anonymous2)块级
                //作用域链<EC(anonymous2),EC(BLOCK)>
                //形参赋值--
                //变量提升--
                //x用的是块级上下文EC(BLOCK)的
                //x = 4
            console.log(x);//4=>此处输出是在块级上下文中执行,输出的是块级上下文中的x
        }
        func(5);
        console.log(x);// 1
    

  • 函数体的大括号在某些情况下也会形成块级上下文

    • 当一个函数 同时满足两个条件,则在本身执行时会形成私有上下文的同时,会再形成一个私有块级上下文【将函数体{}包起来的看作】。【函数执行就有两个上下文了】
      1. 有形参赋值默认值
      2. 函数体中有声明过自己的私有变量【仅限VAR/LET/CONST】【形参赋值和私有声明的变量不同也会生成】
        • FUNCTION只有在声明的名字和形参中的名字相同时,才会单独产生块级上下文】
        • VAR/LET/CONST】无论形参和私有变量声明是否相同都会生成
    • 【新形成的块级上下文】【上级上下文】【函数的私有上下文】,且会把【函数私有上下文】中的形参赋值传递进来,赋值给声明的私有变量