「面试高频」ES6中的let和const

作者: 9ea224c7a546 | 来源:发表于2019-07-01 16:48 被阅读14次

    前言

    let 和 const 是 ES6 中新增的命令,是用于解决 ES5 中使用 var 命令声明变量的一些问题而出现的,
    在了解 let 和 const 之前,我们先来简单回看一下 var 命令的一些不合理的问题,然后再来 了解 let 和 const 的特性。

    var 的问题

    var 主要有如下几个不合理的地方:

    • 没有块级作用域
    • 重复声明
    • 绑定全局作用域
    • 变量提升

    问题:没有块级作用域

    ES5 只有全局作用域和函数作用域,没有块级作用域。

    for(var i = 0; i<10; i++){
        //...    
    }
    console.log(i); // 输出 10
    

    对于有块级作用域的语言来说,for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境中。
    而对于 ES5 中的 var 命令来说,由 for 语句创建的变量 i 即使在 for 循环结束执行后,也依旧存在于循环外部的执行环境中。

    问题:重复声明

    使用 var 语句重复声明变量是合法且无害的。

    var a = 1;
    console.log(a); //print : 1
    var a = 2;
    console.log(a); //print : 2
    var a = 3;
    console.log(a); //print : 3
    

    在大多数语言中,在同一个作用域多次声明同一个变量是非法的,但是在 ES5 中使用 var 命令重复声明的没有任何问题的。

    问题:绑定全局作用域

    在全局作用域下通过 var 命令声明变量,会创建一个新的全局变量作为全局对象的属性。

    var a = 1;
    console.log(window.a); // print: 1
    

    我们仅仅只是在全局作用域中通过 var 声明了一个变量,这个变量居然就挂载到了全局对象上,
    这完全超出了我们的预期,是不是很不合理呢!

    问题:变量提升

    JS引擎在对代码进行编译解释的时候,会查找所有通过 var 命令声明的变量,会将创建变量的时机提升到当前作用域

    第一个例子:

    if( flg ){
        var value = 1;
    }
    console.log(value);
    

    初学者可能会觉得只有 flg 为 true 的时候,才会创建 value,如果 flg 为 false,结果应该是报错,然而因为变量提升的原因,代码相当于:

    var value;
    if (flg) {
        value = 1;
    }
    console.log(value); // flg 为true 的时候,输出 1;flg 为false 的时候,输出 undefined
    

    第二个例子:

    console.log(tmp); // print : undefined
    var tmp = 1;
    console.log(tmp); // print : 1
    

    初学者可能认为我们第一次打印 tmp 的时候应该抛出异常,因为此时 tmp 还没有声明。
    其实不然,因为 ES5 中的 var 命令存在变量提升,会将变量创建和初始化阶段提升至作用域顶部,
    将变量的赋值阶段保留在当前位置。

    let 和 const

    为了解决 var 所产生的种种不合理的情况,ES6 新增了 let 和 const 命令,用于创建安全稳定易理解的变量。

    改进:块级作用域

    ES6 中新增了块级作用域,并且块级作用域可以任意嵌套

    {
        let a = 1;
        console.log(a); // print: 1
    }
    console.log(a); // print error:ReferenceError: a is not defined
    

    这个例子中,通过 let 命令声明的变量 a 仅在块级作用域内生效,所以在块级作用域外引用变量 a 会提示未定义。

    改进:不绑定全局作用域

    let a = 1;
    const b = 1;
    console.log(window.a); // print: undefined
    console.log(window.b); // print: undefined
    

    let 和 const 命令在全局作用域下创建变量,并不会绑定至全局对象中。

    改进:不存在变量提升

    为了纠正 var 命令会发生变量提升的现象,let 和 const 命令改变了语法行为,
    let 和 const 所声明的变量一定要在声明后使用,否则报错。

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    

    上面代码中,变量 foo 用 var 命令声明,会发生变量提升,即脚本开始运行时,变量 foo 已经存在了,但是没有值,所以会输出 undefined。
    变量 bar 用 let 命令声明,不会发生变量提升。这表示在声明它之前,变量 bar 是不存在的,这时如果用到它,就会抛出一个错误。

    改进:不可重复声明

    let 和 const 不允许在相同作用域内,重复声明同一个变量。

    let a = 1;
    var a = 2; //print error : Identifier 'a' has already been declared
    
    let a = 1;
    let a = 2; // print error: Identifier 'a' has already been declared
    

    在函数体中重新声明形参,同样报错

    function fn(params){
        let params = 1;
    } // print error: Identifier 'params' has already been declared
    

    改进:暂时性死区

    暂时性死区(Temporal Dead Zone),简写为 TDZ。

    let 和 const 声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,会导致报错。

    console.log(x); // print error: ReferenceError: value is not defined
    let x = 1;
    

    这是因为 JavaScript 引擎在扫描代码发现变量声明时,要么将它们提升到作用域顶部(遇到 var 声明),
    要么将声明放在 TDZ 中(遇到 let 和 const 声明)。
    访问 TDZ 中的变量会触发运行时错误。
    只有执行过变量声明语句后,变量才会从 TDZ 中移出,然后方可访问。

    简单的来说,只要当前作用域内使用 let 和 const 命令创建了变量,那么在当前作用域的开始直到变量声明的一刻,
    这个阶段就叫做暂时性死区,也就是说从当前作用域的开始到变量创建的一刻,只要使用了这个变量就会报错。

    无法使用和当前作用域内同名的外部变量

    let name = 'tom';
    function fn(){
        console.log(name); // print error : ReferenceError: name is not defined
        let name = 'jack'
    }
    fn();
    

    typeof不再是一个百分之百安全的操作

    typeof x; // ReferenceError
    let x;
    

    变量 x 使用 let 命令声明,所以在声明前,都属于 x 的死区,只要用到变量 x 就会报错。

    let 和 const 的区别

    const 声明的是一个只读常量,一旦声明,常量的值就不能改变。

    const x = 1; 
    x = 2; // print error: TypeError: Assignment to constant variable.
    

    const 声明的时候必须立即初始化,不能等到以后赋值,只声明不赋值,就会报错。

    const y; // print error:SyntaxError: Missing initializer in const declaration
    

    const 实际上保证的并不是变量的值不可改动,而是变量指向的内存地址所保存的数据不可变更。

    对于简单类型,值就保存在变量指向的那个内存地址,因此等同于常量。

    但对于引用类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,
    const 只能保证这个指针是固定的(即总是指向另一个固定的地址),
    至于它指向的数据结构是不是可变的,就完全不能控制了

    const foo = {};
    foo.prop = 123;
    foo.prop // 123
    
    foo = {}; // print error: TypeError: Assignment to constant variable. 
    

    上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。
    不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

    写在最后

    • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞关注
    • 本文同步首发与github,可在github中找到更多精品文章,欢迎Watch & Star ★

    欢迎关注微信公众号【前端小黑屋】,每周1-3篇精品优质文章推送,助你走上进阶之旅

    相关文章

      网友评论

        本文标题:「面试高频」ES6中的let和const

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