ECMAScript 6 Primer I
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
7function 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
5const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
如果连对象的属性也要冻结,可以使用如下代码:1
2
3
4
5
6
7
8var 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
11function* 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
5let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
3.2 对象的解构赋值
对象的解构赋值与数组的不同是,对象的属性没有次序,变量必须与属性同名才能渠道正确的值,如果变量名与属性名不同,必须使用如下方法:1
2
3
4
5
6
7let { 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
9const [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
8let {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
数值和布尔值都能转换成对象,undefined
和null
无法转为对象。
3.5 函数参数的解构赋值
函数的参数也可以使用解构赋值,解构时也可以使用默认值。1
2
3
4
5
6
7
8function 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
2
3
4let x = 1;
let y = 2;
[x, y] = [y, x];函数返回多个值
函数参数的定义
提取 JSON 数据
1
2
3
4
5
6
7
8
9
10let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]函数参数的默认值
遍历 Map 结构
1
2
3
4
5
6
7
8
9const 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的用法?
输入模板的制定方法
1
const { SourceMapConsumer, SourceNode } = require("source-map");
终于知道这样写的原因了。