你好 ES2015

原文: Say Hello To ES2015
笔记:涂鸦码龙

介绍 ES2015

ES2015 是新版的 JavaScript,Node.js 已经完全支持,浏览器端可以用 Babel 库编译。

运行本文的示例代码,可以用 JSBin 环境,也可以结合原文中的测试题检测学习效果。

使用 let 和 const

ES2015 可以用 constlet 替换 var ,它们定义了块级作用域变量。

示例代码:

1
2
3
4
5
for (var lc=0; lc < 10; lc++) {
let value = lc;
}
console.log(value); //抛出错误

变量 value 只能在 for 循环中使用。

constlet 很像,但是它定义的变量值无法改变。

示例代码:

1
2
3
const user = {name: "Arunoda"};
user.name = "Susiripala";
console.log(user.name);

改变的是变量 user 内部的属性,并没有改变 user 本身。

许多人更喜欢用 const 代替 let

使用箭头函数整理你的代码

熟悉的方式:

1
2
3
4
5
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(function(a) {
return a * 10;
});
console.log(multiplyBy10);

使用箭头函数以后:

1
2
3
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(a => a * 10);
console.log(multiplyBy10);

如果方法接受不止1个参数,可以这么写:

1
2
3
const numbers = [10, 20, 30, 50];
const multiplyByIndex = numbers.map((a, i) => a * i);
console.log(multiplyByIndex);

箭头函数返回一个对象的话,需要加圆括号:

1
2
3
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(a => ({res: a * 10}));
console.log(multiplyBy10);

不再使用“self=this”

以前的代码:

1
2
3
4
5
6
7
8
9
10
function Clock() {
this.currentTime = new Date();
}
Clock.prototype.start = function() {
var self = this;
setInterval(function() {
self.currentTime = new Date();
}, 1000);
}

可以用箭头函数代替 self=this

1
2
3
4
5
6
7
8
9
function Clock() {
this.currentTime = new Date();
}
Clock.prototype.start = function() {
setInterval(() => {
this.currentTime = new Date();
}, 1000);
}

setInterval 里面用了箭头函数,它携带了 start 方法的上下文(this)

使用箭头函数要多加小心,并不是随处可用的,箭头函数会携带函数定义时的上下文。

正如这个例子:https://jsbin.com/zuseqap/edit?js,console

改良的对象字面量

在对象里面定义一个方法,可以这么写:

1
2
3
4
5
6
7
const user = {
getName() {
return 'Arunoda';
}
}
console.log(user.getName());

不必每次都写 function 关键字。

这是最酷的特性,你会喜欢的:

1
2
3
4
const name = 'Arunoda';
const age = 80;
const user = {name, age};

瞅瞅多简单,并不用这么啰嗦:

1
2
3
4
5
6
7
const name = 'Arunoda';
const age = 80;
const user = {
name: name,
age: age
};

解构对象

很容易地提取 user 对象的 nameage 字段:

1
2
3
4
5
6
7
8
const user = {
name: 'Arunoda',
age: 80,
city: 'Colombo'
};
const {name, age} = user;
console.log(name, age);

对于函数相当有用,上代码:

1
2
3
4
5
6
7
8
9
10
function printName({name}) {
console.log('Name is: ' + name);
}
const user = {
name: 'Arunoda',
age: 80,
city: 'Colombo'
};
printName(user);

不仅简化了代码,而且可以自描述。看到函数第一行时,我们便会明白使用传入对象的哪个字段。

可以定义传入对象的默认值。

1
2
3
function printUser({name, age = 20}) {
console.log('Name is: ' + name + ' Age: ' + age);
}

像传入对象一样,同样可以从传入的数组中解构值:

1
2
3
4
5
function printUser([name, age = 20]) {
console.log('Name is: ' + name + ' Age: ' + age);
}
printUser(["Arunoda", 80]);

前所未见的方式传递(spread)数组

以前的代码:

1
2
3
4
5
6
7
8
9
10
11
function sum(a, b) {
return a + b;
}
function sumAndLog(a, b) {
var result = sum(a, b);
console.log('Result is: ' + result);
return result;
}
sumAndLog(10, 20);

ES2015 代码:

1
2
3
4
5
6
7
8
9
10
11
function sum(a, b) {
return a + b;
}
function sumAndLog(...args) {
const result = sum(...args);
console.log('Result is: ' + result);
return result;
}
sumAndLog(10, 20);

sumAndLog 方法中使用 spread 操作符(...),可以很简单地把所有参数存入 args 变量,然后再用 spread 操作符把 args 传入 sum 方法。

再看以下例子:

1
2
3
4
5
6
function printTeam(leader, ...others) {
console.log('Leader: ' + leader + ' - Others: ' + others);
}
printTeam('Arunoda', 'John', 'Singh');
//输出结果:"Leader: Arunoda - Others: John,Singh"

克隆、合并对象

以往都是用 underscore 或者 lodash,克隆、合并对象:

1
2
3
4
5
6
var user = {name: "Arunoda"};
var newUser = _.clone(user);
var withAge = _.extend(user, {age: 20});
var newUserVersion = _.defaults({age: 80}, user);
console.log(newUser, withAge, newUserVersion);

ES2015 不需要任何工具库,轻松实现以上功能。

1
2
3
4
5
6
const user = {name: "Arunoda"};
const newUser = {...user};
const withAge = {...user, age: 20};
const newUserVersion = {age: 80, ...user};
console.log(newUser, withAge, newUserVersion);

看以下例子:

1
2
3
4
5
6
7
8
9
10
const user = {
name: 'Arunoda',
emails: ['hello@arunoda.io']
};
const newUser = {...user};
newUser.emails.push('mail@arunoda.io');
console.log(user.emails);
//输出结果:["hello@arunoda.io", "mail@arunoda.io"]

尽管我们克隆了对象,但不是深度克隆,只克隆了顶层字段,emails 数组字段使用的仍是同一个。

往数组里添加元素

跟对象类似,我们同样可以克隆数组:

1
2
3
4
const marks = [10, 20, 30];
const newMarks = [...marks, 40];
console.log(marks, newMarks);

JavaScript 不变性(Immutability)

这些日子,JavaScript 也兴起函数式编程的概念。因此,我们可以尝试写写纯函数。

纯函数:一个函数接收一些值,并且返回一些值,但是通过参数接收到的值不会被改变。 同样的输入总是返回同样的值。
random() 就不是一个纯函数,任何可以修改全局状态的函数都不能称之为纯。

spread 操作符可以轻松实现。

用于对象:

1
2
3
4
5
6
7
8
9
10
11
function addMarks(user, marks) {
return {
...user,
marks
};
}
const user = {username: 'arunoda'};
const userWithMarks = addMarks(user, 80);
console.log(user, userWithMarks);

用于数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
function addUser(users, username) {
const user = {username};
return [
...users,
user
];
}
const user = {username: 'arunoda'};
const users = [user];
const newUsers = addUser(users, 'john');
console.log(users, newUsers);

以 Python 方式合并字符串

合并字符串通常很烦,可以用 + 操作符,或者类似 underscore 的模板。

ES2015 的模板字符串,非常简单,看例子:

1
2
3
4
const name = "Arunoda";
const welcome = `Hello ${name}, Good Morning!`;
console.log(welcome);

注意 “`” 的使用。

多行字符串

既然支持模板字符串,多行字符串也不在话下:

1
2
3
4
5
6
7
const message = `
# Title
This is a multi line string as markdown.
It's pretty nice.
`;
console.log(message);

没有模板字符串的话,是这个样子的:

1
2
var message = "\n # Title\n\n This is a multi line string as markdown.\n It's pretty nice.\n";
console.log(message);

像 Java 一样写 Class

JavaScript 并不是真正的面向对象语言,但是可以用函数和原型模拟类。ES2015 可以写真正原生的类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Vehicle {
constructor(type, number) {
this.type = type;
this.number = number;
}
display() {
return `Number: ${this.number}`;
}
}
const v1 = new Vehicle('Car', 'GH-2343');
console.log(v1.display());

继承一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Vehicle {
constructor(type, number) {
this.type = type;
this.number = number;
}
display() {
return `Number: ${this.number}`;
}
}
class Car extends Vehicle {
constructor(number) {
super('Car', number);
}
display() {
const value = super.display();
return `Car ${value}`;
}
}
const v1 = new Car('GH-2343');
console.log(v1.display());

小汽车继承了车辆:

  • 在 Car constructor 内部调用了 super constructor (Vehicle 的 constructor)。
  • Car 的 display() 方法内部,调用了 super.display() 。这里展示了子类如何继承方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Vehicle {
constructor(type, number) {
this.type = type;
this.number = number;
}
display() {
return `Number: ${this.number}`;
}
}
class Car extends Vehicle {
display() {
const value = super.display();
return `Car ${value}`;
}
}
const v1 = new Car('GH-2343');
console.log(v1.display());

Car 类没有实现 constructor 的话,它会用 Vehicle 的 constructor 。

ES2015 模块系统

ES2015 的模块系统很像 CommonJS 模块系统(或者 Node.js 的模块系统),但是有一点主要的区别:

所有的模块导入应该是静态的,无法在运行时导入模块。编译时间应该完成导入(或者最好在解释 JavaScript 期间完成)。
以下代码在 ES2015 模块里无法使用:

1
2
3
4
5
6
7
let router;
if (typeof window === 'function') {
router = import './client-router';
} else {
router = import './server-router';
}

命名导出函数

定义一个简单的导出函数 sum

1
2
3
export function sum(a, b) {
return a + b;
}

然后导入它:

1
2
3
4
import {sum} from './lib/math';
const total = sum(10, 20);
console.log(total);

导入多个函数:

1
import {sum, multiply}

像函数一样,可以导出任何类型的变量,包括类。

1
2
3
export const BASE = 10;
export let name = 'Arunoda';
export class Vehicle {};

默认导出

有时需要导出一个独立的模块,叫做默认导出。

1
2
3
4
5
6
7
8
9
10
export default class {
constructor(type, number) {
this.type = type;
this.number = number;
}
display() {
return `Number: ${this.number}`;
}
}

可以这么导入:

1
2
3
4
import Vehicle from './lib/vehicle';
const v1 = new Vehicle('Car', 'GH-3355');
console.log(v1.display());

如果再导出一个 print 函数,这么写:

1
import Vehicle, {print} from './lib/vehicle';

这便是我们如何在同一模块导入 “默认导出” 和 “命名导出” 的方式。

重命名导入,一次导入所有命名导出,以及更多知识,参见 MDN 文档

在实际的项目中使用 ES2015?

至今还没有浏览器完全实现 ES2015 的所有规范。因此,无法直接在浏览器里使用 ES2015 。

那么我们该怎么做?

欢迎来到 transpiling 的世界。

现在可以按 ES2015 写代码,然后使用一个工具把它转换成 ES5,最有名的一个 transpiler 便是 Babel

设置 Babel 并没那么简单,需要一定的经验,这是一些新手包,拿去用吧。

此外,可以使用 Meteor,默认支持 ES2015 了。

深入研究 ES2015

深入研究,可以参考以下链接: