ES6标准学习(一) let和const

280 阅读5分钟

前言

本人一枚PHP程序员,最近在学习前端方面的知识,于是淘了本阮一峰老师写的《ES6标准入门》,书中讲的特别好,在此记录下一些get到的知识点,希望对大家也有所帮助。 let和const是此次ES6加入的新命令,大家之前可能或多或少了解它们,但相信大家看完这这篇文章会有更深刻的理解。

1.let命令

1.1 基本用法

  ES6新增了let命令,类似于var用于变量的声明,但是不同于var的全局生效,所声明的变量只在let所在的代码块内有效。

{
    let a = 1;
    var b = 2;
    
    //代码块内部调用
    console.log(a);  //1
    console.log(b);  //2
}
//代码块外部调用
console.log(a);  //ReferenceError: a is not defined
console.log(b);  //2

  上面的代码分别在代码块中用let和var声明了2个变量,然后在代码块内外分别调用这两个变量;在内部都可以正确调用;但是在外部,使用let声明的变量报错,var则正确输出。说明let声明的变量仅在块内部有效。

1.2不存在变量提升

  var命令会发生“变量提升”的现象(变量声明之前即可以去调用,只不过值是undefined),是有悖于逻辑的,不声明居然还能调用,这就很不可思议。 为了纠正这类现象,let改变了语法行为,它声明的变量要在声明后才能使用,不然就直接报错了;

//var的情况
console.log(foo);  //undefined
var foo = 2;

//let的情况
console.log(foo2);  //ReferenceError: Cannot access 'foo2' before initialization
let foo2 = 10;

  foo用var声明会发生变量提升,所以会输出undefined;但是foo2使用let声明则不会出现变量提升,这表示在声明它之前,变量foo2是不存在的,若使用的话就会抛出错误。

1.3暂时性死区

  只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部影响。

//暂时性死区
var tmp = 123;
if (true) {
    tmp = 'abc';  //ReferenceError: Cannot access 'tmp' before initialization
    let tmp;//
}

  上面的代码中存在全局变量tmp,但是在块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6明确规定,如果区块内存在let和const命令,则这个区域对这些命令声明的变量从一开始就形成封闭作用域,只要在声明之前使用这些变量,就会报错。

1.4 不允许重复声明

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

//报错
{
    let a = 10;
    var a = 20;  //SyntaxError: Identifier 'a' has already been declared
}
//报错
() => {
    let a = 10;
    let a = 1;  //SyntaxError: Identifier 'a' has already been declared
}

因此不能在函数内部重新声明参数。

//报错
(a) => {
    let a = 10;  //SyntaxError: Identifier 'a' has already been declared
}

//不报错
(a) => {const PI = 3.1415;

PI = 3//TypeError: Assignment to constant variable.
    {
        let a = 10;
    } 
}

2.const命令

2.1 基本用法

  const声明一个只读的常量,一旦声明,常量的值不能被修改。

const PI = 3.1415;
PI = 3//TypeError: Assignment to constant variable.

从上面的代码可以看出,改变常量的值会报错。

const FOO;  //SyntaxError: Missing initializer in const declaration

const声明常量不赋值也会报错。

2.2 本质

  const本质上并不是保证变量的值不能改动,而是变量指向的内存地址不得改动。对于简单的数据类型(数值、字符串、布尔值)而言,值就保存在变量指向的内存地址中,因此等同于常量。但对于复合类型的数据(对象和数组),变量指向的内存地址只是一个指针,const只能保证这个指针是固定的,不能完全控制它指向的数据结构是不是可变。

const FOO = {};
//为FOO添加一个name属性,可以成功
FOO.name = 'neo';
console.log(FOO.name);  //neo
//将FOO指向另一个对象,报错
FOO = {};  //TypeError: Assignment to constant variable.

  上面的代码中,常量FOO存储的是一个地址,这个地址指向一个对象,不可变的只是这个地址,但对象本身是可变的,依然可以给其添加属性。
来看下数组的例子

const A = [];
A.push('neo');  //可执行
A.length = 0;   //可执行
A = ['aliy'];  //TypeError: Assignment to constant variable.

  上面的代码中,A是一个数组,数组本身是可写的,如果把另外一个数组赋值给A则会报错。

PS:那如何冻结对象,让其无法修改呢? 使用Object.freeze方法。

const FOO = Object.freeze({});
//常规模式下,下面这行代码不生效
//严格模式下,下面这行代码会报错
FOO.name = 'neo';
console.log(FOO)  //输出为{}

  上面的代码中,常量FOO指向一个冻结的对象,所以添加新的属性不起作用,严格模式时还会报错。 除了对象本身冻结,对象的属性也应该冻结,下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {

    Object.freeze(obj);

    Object.keys(obj).forEach(key, i) => {

        iftypeof obj[key] === 'object') {

            constantize(obj[key]);

        }

    });

}

const neo = {name:"neo",test:{}};

constantize(neo)

neo.test.age=23;

console.log(neo)
//若使用constantize函数,则输出值为{ name: 'neo', test: {} }
//反之{ name: 'neo', test: { age: 23 } }

个人总结

  • 在用if、for循环、函数体内最好使用let和const定义变量和常量,可以防止内存外泄(例如for循环中使用var声明变量进行循环,循环结束后该变量是全局变量,一直存在于内存)和变量提升等问题。
  • 使用const定义对象或数组时要注意对应属性的变化。