原文: Say Hello To ES2015
笔记:涂鸦码龙
介绍 ES2015
ES2015 是新版的 JavaScript,Node.js 已经完全支持,浏览器端可以用 Babel 库编译。
运行本文的示例代码,可以用 JSBin 环境,也可以结合原文中的测试题检测学习效果。
使用 let 和 const
ES2015 可以用 const
或 let
替换 var
,它们定义了块级作用域变量。
示例代码:
1 2 3 4 5
| for (var lc=0; lc < 10; lc++) { let value = lc; } console.log(value);
|
变量 value
只能在 for
循环中使用。
const
跟 let
很像,但是它定义的变量值无法改变。
示例代码:
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
对象的 name
和 age
字段:
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');
|
克隆、合并对象
以往都是用 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);
|
尽管我们克隆了对象,但不是深度克隆,只克隆了顶层字段,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 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
深入研究,可以参考以下链接: