ES6学习笔记(let和const命令)

笔记根据阮一峰老师的ECMAScript 6 入门,内容基本来自于此书,总结是为了理解学习和加深印象,把认为重要的部分记录下来。

1. let命令


基本用法

ES6中新增加了let用来声明变量,他和var的用法类似,但是let声明的变量只在let所在的代码块内有效。

1
2
3
4
5
6
7
8
9

{
let a = 10;
var b = 1;
a // 10
}

a // ReferenceError: a is not defined.
b // 1

如上面代码所示,如果在代码块外面调用了let的话,结果会报错。这表明let声明的变量只在他所在的代码块有效。

所以for循环中,我们用let声明会很合适。

1
2
3
4

for( let i = 0; i < arr.length; i++){}

console.log(i)//ReferenceError: i is not defined

上面代码的计数器i,只在for循环内有效。



下面的代码如果使用var,最终的输出结果是10。

1
2
3
4
5
6
7
8

var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10

因为var声明的i在全局作用域中都有效,当在循环当中时a[6]返回function () {console.log(i);};,当在for循环执行完毕后i的值会变成10。当执行a[6]()时结果为10。


如果使用let,声明的变量只在块级作用域内有效,代码如下。

1
2
3
4
5
6
7
8

var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

如上面代码所示,因为i只在本轮循环内有效,所以每一次循环的i其实都是一个新的变量,所以最后结果为6。


不存在变量提升

let不会像var存在变量提升的现象,所以let要在声明之后调用,否则会出现错误。

1
2
3
4
5
6

console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError

var foo = 2;
let bar = 2;

如上代码,在执行代码前,js会提前读取声明的varfunction的变量名(变量值没有预先加载),所以在执行代码前,变量foo已经存在了会输出undefined,而变量bar不会发生变量提升,所以会抛出一个错误。


暂时性死区

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

1
2
3
4
5
6
7

var tmp = 123;

if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

上面代码中,因为块级作用域内let声明了局部变量tmptmp就绑定到了这个块级作用域中,块级作用域外的var声明会失效,在块级作用域内在还没有let声明前就对tmp赋值的话就会报错。

ES6中规定,如果区块中存在letconst命令,这个区块对这两种声明的变量从一开始就形成了封闭的作用域,凡是在声明之前就使用这些变量的话就会报错。

简单来说,在代码块内,如果使用let命令来声明变量,这个变量在声明之前都是不可用的。这个在语法上称为“暂时性死区”(temporal dead zone,简称TDZ)。

1
2
3
4
5
6
7
8
9
10
11
12

if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError

let tmp; // TDZ结束
console.log(tmp); // undefined

tmp = 123;
console.log(tmp); // 123
}

上面的代码中,在let命令声明变量tmp前,都属于tmp的“死区”。

“暂时性死区”也意味着typeof操作会出现错误。

1
2
3

typeof x; // ReferenceError
let x;

但是如果一个变量根本没有被声明,使用typeof反而不会报错。

1
2
//undeclared_variable为为声明变量
typeof undeclared_variable // "undefined"

所以,新语法在有了let声明之后,一定要声明完变量之后再去使用,否则有可能会报错。


还有一些特别的“死区”。

1
2
3
4
5
6

function bar(x = y, y = 2) {
return [x, y];
}

bar(); // 报错

上面代码运行会报错,因为在把y的值赋给x前,y还没有声明,属于死区。

总之,暂时性死区的本质就是,在已进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有在声明变量后才可以获取和使用该变量。


不允许重复声明

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

1
2
3
4
5
6
7
8
9
10
11
12

// 报错
function () {
let a = 10;
var a = 1;
}

// 报错
function () {
let a = 10;
let a = 1;
}

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

1
2
3
4
5
6
7
8
9
10

function func(arg) {
let arg; // 报错
}

function func(arg) {
{
let arg; // 不报错
}
}

2. 块级作用域

为什么需要块级作用域?

在ES5中只有全局作用域和函数作用域,没有块级作用域,这会造成很多问题。

计数的循环变量泄露为全局变量。

1
2
3
4
5
6
7
8

var s = 'hello';

for (var i = 0; i < s.length; i++){
console.log(s[i]);
}

console.log(i); // 5

ES6的块级作用域

let是为JavaScript新增了块级作用域。

1
2
3
4
5
6
7
8

function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}

最后输出的结果为5,这表示let声明的是一个块级作用域,外层的代码块不受内层的影响,如果使用var声明的话,最后输出结果为10。

块级作用域出现后,立即执行匿名函数(IIFE)就不是唯一的选择了。

1
2
3
4
5
6
7
8
9
10
11
12

// IIFE写法
(function () {
var tmp = ...;
...
}());

// 块级作用域写法
{
let tmp = ...;
...
}


3. const命令

const也用来声明变量,这个变量是常量一旦声明,他的值是不可以改变的。

1
2
3
4
5
6
7

'use strict';

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: "PI" is read-only

在严格模式下,改变声明的常量的值会报错。在常规模式下不会报错但也不起作用。

1
2
3
4

const PI = 3.1415;
PI = 3; // 常规模式时,重新赋值无效,但不报错
PI // 3.1415

const声明的变量不可以改变值,所以一旦声明后必须立即初始化。

1
2
3
4

'use strict';

const foo;
// SyntaxError: missing = in const declaration

如上面代码所示,在严格模式下,如果没有立即初始化,会报错。在常规模式下,不会报错,但是以后对foo赋值的话也是无效的。

1
2
3
4

const foo;
foo = 1; // 常规模式,重新赋值无效
foo // undefined

const的作用与和let相同,都是在块级作用域之内有效,并且也不存在声明提升,会出现暂时性死区,只能在声明之后才能够调用。

1
2
3
4
5

if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}

const声明的常量,也和let一样不可以重复声明。

1
2
3
4
5
6
7

var message = "Hello!";
let age = 25;

// 以下两行都会报错
const message = "Goodbye!";
const age = 30;

对于复合型变量名,变量名不会指向数据,而是指向数据所在的地址。const命令可以使指向的地址不变,但是不能保证地址内的数据不变。比如,如果使用const声明了一个对象,其实是声明了一个指向对象的地址,这个地址是不可变的,但是可以为这个对象来添加新的属性。

1
2
3
4
5

const a = [];
a.push("Hello"); // 可执行
a.length = 0; // 可执行
a = ["Dave"]; // 报错

如上,变量a是一个数组,数组本身是可以改写的,但是如果将另一个数组赋值给a,就会报错。

如果想让一个对象,不能添加新的属性,可以使用Object.freeze的方法。

1
2
3
4
5
6

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

如上,添加新的属性不会起作用,在严格模式下还会报错。

ES5声明变量的方法有两个:var,function。ES6除了这两种方法外,还有letconstimportclass这四种方法。


4. 跨模块常量

const声明的常量只在当前代码块有效。如果想设置跨模块的常量,可以采用下面的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

const foo = Object.freeze({});

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

5. 全局对象的属性

ES6规定了,使用varfunction声明的全局变量,依旧是全局对象的属性;使用let命令,const命令和class命令声明的全局变量,不属于全局对象的属性。

1
2
3
4
5
6
7
8

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

let b = 1;
window.b // undefined

以上。