美文网首页
小记1 - let 与 const

小记1 - let 与 const

作者: ilily | 来源:发表于2019-12-11 14:40 被阅读0次

    背景:var声明与变量提升

    众所周知,ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理场景。
    用 var 关键字声明变量,会出现所谓的变量提升。
    即无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。

    示例说明变量提升:
    function getValue(condition) {
    if (condition) {
    var value = "blue";
    // 其他代码
    return value;
    } else {
    // value 在此处可访问,值为 undefined
    return null;
    }
    // value 在此处可访问,值为 undefined
    }
    

    如果不太熟悉 JS ,或许会认为 仅当 condition 的值为 true 时,变量 value 才会被创建。但实际, value 无论如何都会被创建。 JS 引擎在后台对 getValue 函数调整成了下面这样:

    function getValue(condition) {
    var value;
    if (condition) {
    value = "blue";
    // 其他代码
    return value;
    } else {
    return null;
    }
    }
    

    也就是说:value 变量的声明被提升到了顶部,而初始化工作则保留在原处。这意味着:在 else 分支内value 变量也是可访问的,此处它的值会是 undefined ,因为它并没有被初始化。

    为什么需要块级作用域?--先看两种不合理场景

    第一种场景,内层变量覆盖外层变量。

    var tmp = new Date();
    console.log(tmp );  //  Wed Dec 11 2019 10:39:11 GMT+0800 (中国标准时间)
    function f() {
      console.log(tmp);
      if (false) {
        var tmp = 'hello world';
      }
    }
    f(); // undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
    

    第二种场景,用来计数的循环变量泄露为全局变量。

    var s = 'china';
    for (var i = 0; i < s.length; i++) {
      console.log(s[i]);
      console.log(i);
    }
    console.log(i); // 5  
    //由于变量提升,变量i只用来控制循环,但循环结束,它并没消失,泄露成了全局变量。
    

    因此,ES5求我们习惯var声明变量提升,如不理解,就可能导致bug 。
    也因此, ES6 引入了块级作用域,让变量的生命周期更加可控。

    块级作用域和块级声明

    块级声明也就是让所声明的变量在指定块的作用域外无法被访问。
    块级作用域(又称词法作用域)在如下情况被创建:
    1、在一个函数内部
    2、 在一个代码块(由一对花括号包裹)内部
    块级作用域是很多类 C 语言的工作机制, ES6 引入块级声明,给 JS 添加灵活性也与其他语言保持了一致性。

    一、let 声明变量

    基本用法

    let 命令,用来声明变量。用法与 var 类似,基本可用 let 代替 var 声明变量。

    1. let所声明的变量,只在let命令所在的代码块内有效
    2. 不存在变量提升
    3. 不允许(禁止)重复声明
    4. 暂时性死区(TDZ)

    即 let将变量的作用域限制在当前代码块中, let 声明不会被提升到当前代码块顶部,因此需手动将 let 声明放置到顶部,以便让变量在整个代码块内可用。

    // var 的情况-var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况-为纠正var现象,let改变了语法行为,let声明变量一定要声明后使用,否则报错
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    
    //--let不允许在相同作用域内,重复声明同一个变量。
    function func() {
      let a = 10;
      var a = 1;  // 报错
    }
    function func() {
      let a = 10;
      let a = 1; // 报错
    }
    
    //--因此,不能在函数内部重新声明参数。
    function func(arg) {
      let arg;  // 报错
    }
    
    function func(arg) {
      {
        let arg; // 不报错
      }
    }
    func() 
    
    暂时性死区

    ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
    在代码块内,使用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
    }
    

    “暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

    if (condition) {
    console.log(typeof value); // 引用错误
    let value = "blue";
    }
    

    然而,在变量被定义的代码块之外可以对该变量使用 typeof。

    console.log(typeof value); // "undefined"
    if (condition) {
    let value = "blue";
    }
    /*当 typeof 运算符被使用时,value 并没在暂时性死区内,而在定义 value 变量的代码块外。
    这意味着此时并没有绑定 value 变量,而 typeof 仅单纯返回了"undefined" 。
    */
    

    有些“死区”比较隐蔽,不太容易发现。

    function bar(x = y, y = 2) {
      return [x, y];
    }
    bar(); // 报错--因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。
    
    //--如果y的默认值是x,就不会报错,因为此时x已经声明了。
    function bar(x = 2, y = x) {
      return [x, y];
    }
    bar(); // [2, 2]
    
    var x = x;  // 不报错
    let x = x; // ReferenceError: x is not defined  // 报错--与var的行为不同。
    //因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。
    
    

    暂时性死区只是块级绑定的一个独特表现,而另一个独特表现则是在循环时使用它。

    二、const 声明常量

    基本用法

    const 命令,声明的变量会被认为是常量。一旦声明,常量的值就不能改变。
    意味着,const一旦声明变量,就必须立即初始化赋值,不能留到以后赋值。对于const来说,只声明不赋值,就会报错。

    const与let相似之处:
    1. const所声明的变量,只在const声明所在的块级作用域内有效
    2. 不存在变量提升
    3. 不允许(禁止)重复声明
    4. 暂时性死区(TDZ)
    const与let重大区别:

    试图对用const 声明的常量进行重新赋值会抛出错误,无论是在严格模式还是非严格模式下:

    const maxItems = 5;
    maxItems = 6; // 抛出错误
    
    const 本质

    const 保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
    对于简单数据类型(number、string、boolean),值就保存在变量指向的那个内存地址,因此等同于常量。
    但对于复杂数据类型的(主要是对象和数组),变量指向的内存地址(栈内存),保存的只是一个指向实际数据(堆内存)的指针。const只能保证(栈内存中)这个指针是固定的(即总是指向堆内存的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,如果const声明的常量是一个对象,它所包含的值是可以被修改的。

    可以简单理解为:
    const 声明阻止的是 变量绑定与变量自身值的修改,而不阻止对变量成员的修改。

    const person = {
    name: "Nicholas"
    };
    // 工作正常 --修改的是变量成员
    person.name = "Greg";
    
    // 抛出错误 --修改的是变量自身值,所以报错
    person = {
    name: "Greg"
    };
    

    三、块级绑定新的最佳实践

    在 ES6 的发展阶段,被广泛认可的变量声明方式是:默认情况下应当使用 let 而不是 var。对于多数 JS 开发者来说, let 的行为方式正是 var 本应有的方式,因此直接用 let 替代 var 更符合逻辑。在这种情况下,你应当对需要受到保护的变量使用 const 。

    一种替代方案更为流行,那就是在默认情况下使用 const 、并且只在知道变量值需要被更改的情况下才使用 let 。
    其理论依据是大部分变量在初始化之后都不应当被修改,因为预期外的改动是 bug 的源头之一。这种理念有着足够强大的吸引力,是值得在代码中照此进行探索实践的。

    小结

    1. let 与 const所声明的变量,只在它们声明所在的块级作用域内有效
    2. let 与 const 块级声明都不会进行变量提升
    3. let 与 const 块级声明都禁止重复声明
    4. let 与 const 都存在暂时性死区:不能在变量声明位置之前访问。
      即便使用的是 typeof 这样的安全运算符。由于块级绑定存在暂时性死区( TDZ ),试图在声明位置之前访问它就会导致错误。-副作用
    5. 另 const声明的变量必须初始化,且不得对变量重新赋值,否则报错。
    块级绑定当前的最佳实践

    默认情况下使用 const ,在你知道变量值需要被更改的情况下才使用 let 。
    这在代码中能确保基本层次的不可变性,有助于防止某些类型的错误。

    ES6 声明变量的六种方法

    ES5 只有两种声明变量的方法:var命令和function命令。
    ES6 添加 let和const命令 及另两种声明变量的方法:import和class命令。
    所以,ES6 一共有 6 种声明变量的方法。

    相关文章

      网友评论

          本文标题:小记1 - let 与 const

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