ES6_let&const

187 阅读5分钟

一、回顾 var

变量声明提升、可重复定义、全局挂载到window上

作用域:全局作用域、局部(函数)作用域

二、let

不会变量声明提升、不能重复定义、不挂载到window上

加强对作用域的控制:let配合{}使用产生块级作用域,块里可以访问块外的

1. 变量不提升

  • 由于变量不提升,会产生暂时性死区问题:
// 临时死区:
let a = 20;
{
    console.log(a);
    let a = 10;
}
// 结果报错为a is not undefined
// 因为只要里面有a,他就在里面称霸了,不会让你访问外面的。

底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错“Cannot access 'a' before initialization” 当代码运行到该变量的声明语句事,会将其从暂时性死区中移除。

  • 由于暂时性死区,所以typeof不再绝对安全
// typeof 之前没定义的变量都返回undefined,但是现在有可能报错
console.log(typeof a);  // cannot access 'a' before initialization
let a = 1;

所以,相比较而言,如果一个变量根本没有被声明,使用typeof 反而不会报错

  • 解决闭包问题
for(let i = 0; i < 10; i++){
    setTimeout(function () {
        console.log(i);
    },0)
}
// 用var导致闭包的原因,是因为这里的变量提升了。每次i改变的都是上面的i
// 用let就能够解决了。 

for循环时,给人感觉上let i 也没有写在大括号中。应该也是全局变量呀!

其实,循环这里会进行特殊处理:在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)。在循环中使用let声明的循环变量,在循环结束后会被销毁。

2. 不能重复声明

// 以下情况都会报错
function func() {
    let a = 10;
    var a = 1;
}

function func(arg) {
    let arg;
}

3. 块级作用域

  • 没有块级作用域会产生的问题:

    • 内层变量可能会覆盖外层变量
    • 用来计数的循环变量泄露位全局变量
  • 之前没有块级作用域之前,想把变量放到作用域中,而不暴露到全局上,一般都是用立即执行函数(IIFE)。现在有了块级作用域,使得立即执行函数就不再必要了。

  • 块级作用域里的函数声明

ES5规定,函数只能在顶层作用域和函数作用域中声明,不能在会计作用域声明(比如:if、try语句)。但是浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域中声明函数(所以需要注意,在if、try里声明的函数会提升)。

ES6引入了块级作用域,明确允许在块级作用域中声明函数。ES6规定,在块级作用域中,还是声明语句的行为类似于let,在块级作用域之外不可引用。

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

以上代码在ES5中运行得到'I am inside',因为if内声明的函数f会提升到函数头部

在ES6中运行理论上得到'I am outside'。但是实际上会报错。为啥?

因为ES6中如果改变块级作用域内声明的函数的处理规则,回对旧代码产生影响,为了兼容,ES6在附录中规定,浏览器可以不遵守上面的规定,具体规则如下:

- 允许在块级作用域内声明函数
- 函数声明类似于var(相当于函数表达式,只提升变量),即会提升到全局作用域或函数作用域头部。
- 同时,函数声明还会提升到所在的块级作用域的头部。

所以以上代码在ES6中相当于如下执行:

function f() { console.log('I am outside!'); }
(function(){
    var f;
    if (false) {
        f = function () {
            console.log('I am inside!');
        }
    }
    f();
}());
// 在ES6环境下执行:
console.log(a);
{
    function a () {

    }
}
// 这种情况打印undefined,
// 因为里面相当于写成了 var a = function(){}

三、const

const用于声明一个常量

和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。

对于const而言,只声明不赋值就会报错。

实际上在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改。

注意的细节:

  1. 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变

  2. 常量的命名

    1. 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距离或其他一些绝不可能变化的配置。
      • 通常,该常量的名称全部使用大写,多个单词之间用下划线分割
    2. 普通的常量:使用和之前一样的命名即可
  3. 在for循环中,循环变量不可以使用常量。for in循环中可以使用

存储常量的空间里面的值不能发生改变。但是对于引用值来说,引用值可以改,因为空间里面的值是引用值的地址

const保证的是变量指向的内存地址不得改动。如果真的想冻结对象,应该使用Object.freeze方法。

const foo = Object.freeze({});
// 常规模式下,下面一行不起作用
// 严格模式下,改行会报错
foo.prop = 123;

出了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

var constantize = obj => {
    Object.freeze(obj);
    Object.keys(obj).forEach( (key, i) => {
        if (typeof obj[key] === 'object') {
            constantize(obj[key]);
        }
    })
}

四、ES6声明变量的6种方法

var、function、let、const、import、class