ECMAScript 6 Primer I

Posted by Gloomymoon on 2018-02-08

ECMAScript 6 Primer I

《ES6标准入门》
——阮一峰

1 ECMAScript 6 简介

ECMAScript 6.0是JavaScript语言的下一代标准,已经在2015年6月正式发布,每年都会发布一个标准升级版,根据计划2017年6月发布了ES2017标准,ES6涵盖了ES2015、ES2016、ES2017等。

2 let 和 const 命令

2.1 let 命令

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

let声明的变量一定要在声明后才能使用,否则报错,而不像var声明的变量在声明前可以使用(值为undefined),即不存在变量提升。因此在代码块内,使用let声明变量前的代码区间称为“暂时性死区”(temporal dead zone,TDZ)。TDZ意味着typeof不再是一个安全的操作。

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

2.2 块级作用域

let实际上为JavaScript带来了块级作用域。外层代码块不受内层代码块的影响。

1
2
3
4
5
6
7
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}

ES6允许块级作用域的任意嵌套。块级作用域的出现,使得广泛应用的立即执行函数表达式(IIFE)不再必要了。

1
2
3
4
5
6
7
8
9
10
11
// IIFE 写法
(function () {
var tmp = ...;
...
}());

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

考虑到兼容ES5的旧代码,应该避免在块级作用域内生命函数,如果确实需要,应该写成函数表达式而不是函数声明语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}

// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}

2.3 const 命令

const声明一个常量,其他特性与let声明的变量类似。

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

如果想要冻结对象,应该使用Object.freeze方法。

1
2
3
4
5
const foo = Object.freeze({});

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

如果连对象的属性也要冻结,可以使用如下代码:

1
2
3
4
5
6
7
8
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};

2.4 顶层对象属性

由于浏览器、Web Worder和Node里顶层对象不统一,很难找到一种方法可以在所有情况系都取到顶层对象。

具体可以参见这里

3 变量的解构赋值

3.1 数组的解构赋值

ES6允许按照一定的模式,从数组和对象中提取值。

1
let [a, b, c] = [1, 2, 3];

本质上只要等号两边的模式相同,左边的变量就会被赋予对应的值,匹配可以是完全的也可以是不完全的,如果解构不成功,变量的值就等于undefined

只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

1
2
3
4
5
6
7
8
9
10
11
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5

解构赋值允许指定默认值,ES6使用严格相等运算符(===)判断一个位置是否有值,所有只有当一个数组成员严格等于undefined时,默认赋值才会生效。

1
2
3
4
5
let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

3.2 对象的解构赋值

对象的解构赋值与数组的不同是,对象的属性没有次序,变量必须与属性同名才能渠道正确的值,如果变量名与属性名不同,必须使用如下方法:

1
2
3
4
5
6
7
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

如果将一个已经声明的变量用于解构赋值,必须非常小心。

1
2
3
4
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的报错是因为JavaScript引擎会将{x}理解成为一个代码块,正确的方法是放在一个圆括号内。

1
2
3
// 正确的写法
let x;
({x} = {x: 1});

3.3 字符串的解构赋值

字符串也可以解构赋值,因为它可以被转换成一个类似数组的对象。

1
2
3
4
5
6
7
8
9
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length : len} = 'hello';
len // 5

3.4 数值和布尔值的解构赋值

结构赋值时,如果等号右边不是对象或数组,都会先将其转为对象,无法转为对象的,解构赋值会报错。

1
2
3
4
5
6
7
8
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

数值和布尔值都能转换成对象,undefinednull无法转为对象。

3.5 函数参数的解构赋值

函数的参数也可以使用解构赋值,解构时也可以使用默认值。

1
2
3
4
5
6
7
8
function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

注意使用默认值的写法,如果使用{x, y} = { x: 0, y: 0 }将不能正确地对{}参数赋默认值。

3.6 圆括号问题

解构赋值使用起来方便,但是对于编译器来说,一个式子到底是模式还是表达式,解析起来并不容易。ES6的规则是,只要有可能导致结构的歧义,就不得使用圆括号,因此建议只要有可能,就不要在模式中放置圆括号。

可以使用圆括号的情况只有一种:赋值语句的非模式部分。

1
2
3
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确

3.7 用途

变量解构赋值的用途很多,理解之后使用起来感觉像Python的切片方法一样灵活。

  1. 交换变量的值

    1
    2
    3
    4
    let x = 1;
    let y = 2;

    [x, y] = [y, x];
  2. 函数返回多个值

  3. 函数参数的定义

  4. 提取 JSON 数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
    };

    let { id, status, data: number } = jsonData;

    console.log(id, status, number);
    // 42, "OK", [867, 5309]
  5. 函数参数的默认值

  6. 遍历 Map 结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');

    for (let [key, value] of map) {
    console.log(key + " is " + value);
    }
    // first is hello
    // second is world

    像不像Python的用法?

  7. 输入模板的制定方法

    1
    const { SourceMapConsumer, SourceNode } = require("source-map");

    终于知道这样写的原因了。