zoukankan      html  css  js  c++  java
  • ES6 |Let与Const

    let和const命令

    ES6 2.png

    Let

    声明的变量只在let命令所在的代码块内有效

    {
      let a = 10;
      var b = 1;
    }
    
    a // ReferenceError: a is not defined.
    b // 1
    

    for循环

    for (let i = 0; i < 10; i++) {}
    console.log(i);		//i is not defined
    

    对比

    //var声明时
    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 10
    //解析:i是var声明的全局变量。循环内i被赋给数组a的function在运行时,通过闭包会读到同一个变量i,导致最后输出的是最后一轮的i的值,也就是10
    
    //let声明时
    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
    //解析:let声明的i仅在块级作用域内有效。当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
    

    循环语句是一个父作用域,循环体内部是一个单独的子作用域。函数内部的变量i和外部的变量i是分离

    for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc
    

    不存在变量提升

    var声明的变量可以在声明之前使用,预解析会把声明提前,即变量提升。

    console.log(bar)	//undefined
    var bar=1
    
    //实际上var变量提升了
    var bar
    console.log(bar)	//undefined
    a=1
    

    let所声明的变量一定要在声明后使用,否则报错

    console.log(bar); // 报错ReferenceError
    let bar = 2;
    

    暂时性死区

    一旦某区块存在let命令,这个区块就对该变量形成封闭作用域。凡是在声明之前就使用这些变量,就会报错。这在语法上称为暂时性死区(temporal dead zone,简称 TDZ)

    if (true) {
      // TDZ开始
      tmp = 'abc'; // ReferenceError
      console.log(tmp); // ReferenceError
    
      let tmp; // TDZ结束
      console.log(tmp); // undefined
    
      tmp = 123;
      console.log(tmp); // 123
    }
    

    不允许重复声明

    //不允许在相同作用域内重复声明同一个变量
    function () {
      let a = 10;
      var a = 1;
    }	//报错
    
    //不能在函数内部重新声明参数
    function func(arg) {
      let arg; // 报错	
    }
    
    function func(arg) {
      {
        let arg; // 不报错	
      }
    }
    

    块级作用域

    //ES6 允许块级作用域的任意嵌套
    {{{{{let insane = 'Hello World'}}}}};
    
    //外层作用域无法读取内层作用域的变量
    {{{{
      {let insane = 'Hello World'}
      console.log(insane); // 报错
    }}}};
    
    //内层作用域可以定义外层作用域的同名变量
    {{{{
      let insane = 'Hello World';
      {let insane = 'Hello World'}
    }}}};
    

    块级作用域与函数声明

    • 在ES5中:

      function f() { console.log('I am outside!'); }
      (function () {
        if (false) {
          function f() { console.log('I am inside!'); }// 重复声明一次函数f
        }
        f();		//“I am inside!”
      }());
      

      为什么呢?实际上,在if内声明的函数f会被提升到函数头部,如下

      function f() { console.log('I am outside!'); }
      (function () {
        function f() { console.log('I am inside!'); }
        if (false) {}
        f();		//“I am inside!”
      }());
      
    • 在es6中:

      es6规定:允许在块级作用域之中声明函数,行为类似于let,在块级作用域之外不可引用

      function f() { console.log('I am outside!'); }
      
      (function () {
       if (false) {
           function f() { console.log('I am inside!'); }
       }
      f();
      }());
      

      这理论上会得到“I am outside!”。但是如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式

      允许在块级作用域内声明函数,类似于var,即会提升到全局作用域、函数作用域或所在块级作用域的头部

      规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理,根据浏览器的 ES6 环境规则:

      function f() { console.log('I am outside!'); }
      (function () {
          if (false) {
              function f() { console.log('I am inside!'); }
          }
          f();		//f is not a function
      }());
      
      //因为实际上运行的是
      function f() { console.log('I am outside!'); }
      (function () {
          var f = undefined;
          if (false) {
              function f() { console.log('I am inside!'); }
          }
          f();
      }());
      

      考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句

      // 函数声明语句---不推荐
      {
        let a = 'secret';
        function f() {
          return a;
        }
      }
      
      // 函数表达式---推荐
      {
        let a = 'secret';
        let f = function () {
          return a;
        };
      }
      

    do 表达式

    块级作用域是一个语句,没有返回值

    {
      let t = f();
      t = t * t + 1;
    }		//无返回值
    

    通过do把块级作用域变为do表达式,也就是说可以返回值

    let x = do {
      let t = f();
      t * t + 1;
    };		//变量x会得到整个块级作用域的返回值
    

    Const 命令

    特性

    • const声明一个只读的常量,一旦声明,常量的值就不能改变,且必须立即初始化,不能留到以后赋值。

    • const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。也与let一样不可重复声明

    本质

    • const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

    • 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

    • 但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,const只能保证这个指针是固定的,不能控制数据结构

      const foo = {};
      
      // 为 foo 添加一个属性,可以成功
      foo.prop = 123;
      foo.prop // 123
      
      // 将 foo 指向另一个对象,就会报错
      foo = {}; // TypeError: "foo" is read-only
      

      如果真的想将对象冻结,应该使用Object.freeze方法

      const foo = Object.freeze({});
      

    ES6 声明变量的六种方法

    • ES5 只有两种声明变量的方法:var命令和function命令
    • E6除了添加letconst命令;import命令和class命令

    顶级对象的属性

    顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量是等价的

    window.a = 1;
    a // 1
    
    a = 2;
    window.a // 2
    

    但这样会带来很多问题:

    • 首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)
    • 程序员很容易不知不觉地就创建了全局变量
    • 顶层对象的属性是到处可以读写的,这非常不利于模块化编程
    • window对象有实体含义,指的是浏览器的窗口对象,不合适。

    为了解决这些问题,ES6规定

    var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。

    var a = 1;
    // 如果在Node的REPL环境,可以写成global.a
    // 或者采用通用方法,写成this.a
    window.a // 1
    
    let b = 1;
    window.b // undefined
    

    global对象

    ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

    • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
    • 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self
    • Node 里面,顶层对象是global,但其他环境都不支持。

    同一段代码为了能够在各种环境都能取到顶层对象,一般使用this变量,但是有局限性

    • 全局环境中,this会返回顶层对象。但是,Node模块和ES6模块中,this返回的是当前模块。
    • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined
    • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么evalnew Function这些方法都可能无法使用。

    现在有提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象

  • 相关阅读:
    打印机共享为什么老是出现“操作无法完成(错误 0X00000709)。再次检查打印机名称、并确保打印机连接网络
    给UITextField设置头或尾空白
    Objective-C中的关联(objc_setAssociatedObject,objc_getAssociatedObject,objc_removeAssociatedObjects)
    定时器在多线程中的使用
    block知识点
    UIViewController新方法的使用(transitionFromViewController:toViewController:duration:options:animations:completion:)
    NSMutableAttributedString 的使用方法,设置格式
    statusbar的颜色设置
    获取图片的缩略图
    彻底理解position与anchorPoint
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13595628.html
Copyright © 2011-2022 走看看