ES6常用新特性——读《Understanding ECMAScript 6》总结

现在ES6在很多项目中大量使用。最近我也花时间看了一下《Understanding ECMAScript6》的中文电子书。在这里总结了一些在实际开发中常用的新特性。

块级作用域

在ES6之前,JS只有一种变量声明方式——使用 var 关键字声明的变量。这种声明变量的方式,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。这就是所谓的变量提升 hoisting )。
ES6 引入了块级作用域,让变量的生命周期更加可控。

块级声明

块级声明也就是让所声明的变量在指定块的作用域外无法被访问。块级作用域(又被称为词法作用域)在如下情况被创建:

  1. 在一个函数内部
  2. 在一个代码块(由一对花括号包裹)内部

    let声明

    let声明会将变量的作用域限制在当前代码块中。由于 let 声明并不会被提升到当前代码块的顶部,因此你需要手动将 let 声明放置到顶部,以便让变量在整个代码块内部可用。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function getValue(condition) {
    if (condition) {
    let value = "blue";
    // 其他代码
    return value;
    } else {
    // value 在此处不可用
    return null;
    }
    // value 在此处不可用
    }

注意事项
如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 let 声明就会导致抛出错误。例如:

1
2
3
var count = 30;
let count = 40; // 语法错误

另一方面,在嵌套的作用域内使用 let 声明一个同名的新变量,则不会抛出错误,以下代码对此进行了演示:

1
2
3
4
5
6
7
var count = 30;
// 不会抛出错误
if (condition) {
let count = 40;
// 其他代码
}

常量声明

在 ES6 中里也可以使用 const 语法进行声明。使用 const 声明的变量会被认为是常量( constant )。const 用法与 let 类似,但有一个重要的区别,const 声明的变量的值在被设置完成后就不能再被改变。正因为如此,所有的 const 变量都需要在声明时进行初始化

1
2
3
4
5
6
7
8
const maxItems = 30; // 有效的常量
const name; // 语法错误:未进行初始化
const minItems = 5;
minItems = 6; //抛出错误

模板字符串

模板字符串(template string)是增强版的字符串,使用反引号( ` )来包裹普通字符串。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 普通字符串
let message = `Hello world!`;
console.log(message); // Hello world!
//在字符串中包含反引号,只需使用反斜杠( \ )转义即可
let message = `\`Hello\` world!`;
console.log(message); // `Hello` world!
// 多行字符串(只需在想要的位置包含换行即可)
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 16
//反引号之内的所有空白符都是字符串的一部分,因此需要留意缩进。
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 31
// 字符串中嵌入变量
var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`) // Hello Bob, how are you today?

替换位

模板字符串替换位的标识是 ${} 。大括号内部可以放入任意的JavaScript表达式,比如:变量名、运算、函数调用,以及引用对象属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//普通变量名
var name = "Nicholas";
var message = `Hello, ${name}.`;
console.log(message); // Hello, Nicholas.
//计算
var x = 1;
var y = 2;
console.log(`${x} + ${y} = ${x + y}`) // 1 + 2 = 3
console.log(`${x} + ${y * 2} = ${x + y * 2}`) // 1 + 4 = 5
//函数调用
function fn() {
return "Hello World";
}
console.log(`foo ${fn()} bar`) // foo Hello World bar
//对象属性
var obj = {x: 1, y: 2};
console.log(`${obj.x + obj.y}`)

函数

函数参数的默认值

在 ES5 或更早的版本中,我们可能会使用下述模式来创建带有参数默认值的函数:

1
2
3
4
5
6
7
function add(x, y) {
x = x || 20;
y = y || 30;
return x + y;
}
console.log(add()); // 50

这种写法有一个缺点:如果参数x或者y赋值了,但是对应的布尔值为false,则该赋值不起作用。
在这种情况下,更安全的替代方法是使用typeof来检测参数的类型,示例如下:

1
2
3
4
5
6
function add(x, y) {
x = (typeof x !== "undefined") ? x : 20;
y = (typeof y !== "undefined") ? x : 30;
//...
}

下面来看看ES6函数参数默认值的写法:

1
2
3
function add(x = 20, y = 30) {
return x + y;
}

可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。

rest参数和扩展运算符

关于这两部分内容可以看这里

箭头函数

ES6 最有意思的一个新部分就是箭头函数( arrow function )。箭头函数使用“箭头”(=>)来定义。
先来看看箭头函数与传统的函数写法的区别:

1
2
3
4
5
6
7
8
9
10
11
// ES6
var f = () => 5;
// ES5
var f = function () { return 5 };
// ES6
var sum = (num1, num2) => num1 + num2;
// ES5
var sum = function(num1, num2) {
return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

1
2
3
4
5
const foo = () => {
const a = 20;
const b = 30;
return a + b;
}

箭头函数的一个用处是简化回调函数。

1
2
3
4
5
6
7
// ES5
[1,2,3].map(function (x) {
return x * x;
});
// ES6
[1,2,3].map(x => x * x);

箭头函数可以替换函数表达式,但是不能替换函数声明

在使用箭头函数时要注意如下几点:

  • 不能更改this :this的值在函数内部不能被修改,在函数的整个生命周期内其值会
    保持不变。
  • 没有arguments对象:既然箭头函数没有arguments绑定,你必须依赖于具名参数或
    剩余参数来访问函数的参数。
  • 不能被使用new调用: 箭头函数没有[[Construct]]方法,因此不能被用为构造函
    数,使用new调用箭头函数会抛出错误。
  • 没有原型: 既然不能对箭头函数使用new,那么它也不需要原型,也就是没有
    prototype属性。

对象字面量语法的扩展

属性和方法的简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 属性的简写
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
// 方法的简写
var o = {
method() {
return "Hello!";
}
};
// 等同于
var o = {
method: function() {
return "Hello!";
}
};

需计算属性名

JavaScript语言定义对象的属性,有两种方法。

1
2
3
4
5
6
7
8
9
10
var person = {},
lastName = "last name";
// 方法一
person["first name"] = "Nicholas";
// 方法二
person[lastName] = "Zakas";
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

但是,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一定义属性。

1
2
3
4
5
var person = {
"first name": "Nicholas"
};
console.log(person["first name"]); // "Nicholas"

在ES6中,需计算属性名是对象字面量语法的一部分,它用的也是方括号表示法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var lastName = "last name";
var person = {
"first name": "Nicholas",
[lastName]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
// 方括号内也可以是表达式
var suffix = " name";
var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"
// 也可以用来表示方法名
var obj = {
['h' + 'ello']() {
console.log('hi');
}
};
obj.hello() // hi

解构赋值

解构赋值也是ES6中非常常用的一个特性。
按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

对象解构

对象解构语法在赋值语句的左侧使用了对象字面量,例如:

1
2
3
4
5
6
7
8
9
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"

代码中,node.type的值被存储到type本地变量中,node.name的值则存储到name变量中。

当使用解构赋值语句时,如果所指定的本地变量在对象中没有找到同名属性,那么该变量会被赋值为undefined。例如:

1
2
3
4
5
6
7
8
9
10
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined

我们可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。就像这样:

1
2
3
4
5
6
7
8
9
10
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true

上面的示例都使用了对象中的属性名作为本地变量的名称。但ES6允许我们在给本地变量赋值时使用一个不同的名称。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let node = {
type: "Identifier",
name: "foo"
};
let { type: localType, name: localName } = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
// 我们也可以给变量别名加默认值
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"

数组结构

数组解构的语法看起来与对象解构非常相似,只是将对象字面量替换成了数组字面量。直接看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
// 也可以在解构模式中忽略一些项
let colors = [ "red", "green", "blue" ];
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
// 也可以添加默认值
let colors = [ "red" ];
let [ firstColor, secondColor = "green" ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

字符串结构

字符串也可以进行结构赋值。

1
2
3
4
5
6
7
const [a, b, c, d, e] = 'hello';
console.log(a) // "h"
console.log(b) // "e"
console.log(c) // "l"
console.log(d) // "l"
console.log(e) // "o"

参数结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
// 参数解构也可以有默认
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]

模块

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

export 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 导出数据
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 导出函数
export function sum(num1, num2) {
return num1 + num1;
}
// 导出类
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// export还可以像下面这样写,放在大括号内统一导出
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
export {color, name, magicNumber};
// 重命名导出
function sum(num1, num2) {
return num1 + num1;
}
export {sum as add} // 这里sum函数被作为add导出

import 命令

1
2
3
4
5
6
7
8
9
10
11
// 导入单个
import { color } from "./example.js";
// 导入多个
import { color, name, sum } from "./example.js";
// 重命名导入
import { color as redColor } from "./example.js";
// 整体导入
import * as example from "./example.js";

###导出/导入默认值###
导出默认值要使用default关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 导出默认值一共有三种写法
// 第一种
export default function(num1, num2) {
return num1 + num2;
}
// 第二种
function sum(num1, num2) {
return num1 + num2;
}
export default sum;
// 第三种
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };

导入默认值得方式也有所不同

1
2
3
import sum from "./example.js"; // 与前面不同的是,这里没有了大括号。
console.log(sum(1, 2)); // 3

后记

上面只是总结得只是一部分ES6的常用特性,其实还有PromiseClass等,因篇幅原因,这些可能留到以后再写。