原文: Say Hello To ES2015
笔记:涂鸦码龙
ES2015 是新版的 JavaScript,Node.js 已经完全支持,浏览器端可以用 Babel 库编译。
运行本文的示例代码,可以用 JSBin 环境 ,也可以结合原文中的测试题检测学习效果。
ES2015 可以用 const
或 let
替换 var
,它们定义了块级作用域变量。
示例代码:
for (var lc=0; lc < 10; lc++) {
let value = lc;
}
console.log(value); //抛出错误
变量 value
只能在 for
循环中使用。
const
跟 let
很像,但是它定义的变量值无法改变。
示例代码:
const user = {name: "Arunoda"};
user.name = "Susiripala";
console.log(user.name);
改变的是变量 user
内部的属性,并没有改变 user
本身。
许多人更喜欢用 const
代替 let
。
熟悉的方式:
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(function(a) {
return a * 10;
});
console.log(multiplyBy10);
使用箭头函数以后:
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(a => a * 10);
console.log(multiplyBy10);
如果方法接受不止1个参数,可以这么写:
const numbers = [10, 20, 30, 50];
const multiplyByIndex = numbers.map((a, i) => a * i);
console.log(multiplyByIndex);
箭头函数返回一个对象的话,需要加圆括号:
const numbers = [10, 20, 30, 50];
const multiplyBy10 = numbers.map(a => ({res: a * 10}));
console.log(multiplyBy10);
以前的代码:
function Clock() {
this.currentTime = new Date();
}
Clock.prototype.start = function() {
var self = this;
setInterval(function() {
self.currentTime = new Date();
}, 1000);
}
可以用箭头函数代替 self=this
:
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
在对象里面定义一个方法,可以这么写:
const user = {
getName() {
return 'Arunoda';
}
}
console.log(user.getName());
不必每次都写 function
关键字。
这是最酷的特性,你会喜欢的:
const name = 'Arunoda';
const age = 80;
const user = {name, age};
瞅瞅多简单,并不用这么啰嗦:
const name = 'Arunoda';
const age = 80;
const user = {
name: name,
age: age
};
很容易地提取 user
对象的 name
和 age
字段:
const user = {
name: 'Arunoda',
age: 80,
city: 'Colombo'
};
const {name, age} = user;
console.log(name, age);
对于函数相当有用,上代码:
function printName({name}) {
console.log('Name is: ' + name);
}
const user = {
name: 'Arunoda',
age: 80,
city: 'Colombo'
};
printName(user);
不仅简化了代码,而且可以自描述。看到函数第一行时,我们便会明白使用传入对象的哪个字段。
可以定义传入对象的默认值。
function printUser({name, age = 20}) {
console.log('Name is: ' + name + ' Age: ' + age);
}
像传入对象一样,同样可以从传入的数组中解构值:
function printUser([name, age = 20]) {
console.log('Name is: ' + name + ' Age: ' + age);
}
printUser(["Arunoda", 80]);
以前的代码:
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 代码:
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
方法。
再看以下例子:
function printTeam(leader, ...others) {
console.log('Leader: ' + leader + ' - Others: ' + others);
}
printTeam('Arunoda', 'John', 'Singh');
//输出结果:"Leader: Arunoda - Others: John,Singh"
以往都是用 underscore 或者 lodash ,克隆、合并对象:
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 不需要任何工具库,轻松实现以上功能。
const user = {name: "Arunoda"};
const newUser = {...user};
const withAge = {...user, age: 20};
const newUserVersion = {age: 80, ...user};
console.log(newUser, withAge, newUserVersion);
看以下例子:
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 数组字段使用的仍是同一个。
跟对象类似,我们同样可以克隆数组:
const marks = [10, 20, 30];
const newMarks = [...marks, 40];
console.log(marks, newMarks);
这些日子,JavaScript 也兴起函数式编程的概念。因此,我们可以尝试写写纯函数。
纯函数:一个函数接收一些值,并且返回一些值,但是通过参数接收到的值不会被改变。 同样的输入总是返回同样的值。random() 就不是一个纯函数,任何可以修改全局状态的函数都不能称之为纯。
用 spread
操作符可以轻松实现。
用于对象:
function addMarks(user, marks) {
return {
...user,
marks
};
}
const user = {username: 'arunoda'};
const userWithMarks = addMarks(user, 80);
console.log(user, userWithMarks);
用于数组:
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);
合并字符串通常很烦,可以用 + 操作符,或者类似 underscore
的模板。
ES2015 的模板字符串,非常简单,看例子:
const name = "Arunoda";
const welcome = `Hello ${name}, Good Morning!`;
console.log(welcome);
注意 “`” 的使用。
既然支持模板字符串,多行字符串也不在话下:
const message = `
# Title
This is a multi line string as markdown.
It's pretty nice.
`;
console.log(message);
没有模板字符串的话,是这个样子的:
var message = "/n # Title/n/n This is a multi line string as markdown./n It's pretty nice./n";
console.log(message);
JavaScript 并不是真正的面向对象语言,但是可以用函数和原型模拟类。ES2015 可以写真正原生的类了。
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());
继承一个类:
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());
小汽车继承了车辆:
super constructor
(Vehicle 的 constructor)。 super.display()
。这里展示了子类如何继承方法。 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 的模块系统很像 CommonJS 模块系统(或者 Node.js 的模块系统),但是有一点主要的区别:
所有的模块导入应该是静态的,无法在运行时导入模块。编译时间应该完成导入(或者最好在解释 JavaScript 期间完成)。以下代码在 ES2015 模块里无法使用:
let router;
if (typeof window === 'function') {
router = import './client-router';
} else {
router = import './server-router';
}
定义一个简单的导出函数 sum
:
export function sum(a, b) {
return a + b;
}
然后导入它:
import {sum} from './lib/math';
const total = sum(10, 20);
console.log(total);
导入多个函数:
import {sum, multiply}
像函数一样,可以导出任何类型的变量,包括类。
export const BASE = 10;
export let name = 'Arunoda';
export class Vehicle {};
有时需要导出一个独立的模块,叫做默认导出。
export default class {
constructor(type, number) {
this.type = type;
this.number = number;
}
display() {
return `Number: ${this.number}`;
}
}
可以这么导入:
import Vehicle from './lib/vehicle';
const v1 = new Vehicle('Car', 'GH-3355');
console.log(v1.display());
如果再导出一个 print
函数,这么写:
import Vehicle, {print} from './lib/vehicle';
这便是我们如何在同一模块导入 “默认导出” 和 “命名导出” 的方式。
重命名导入,一次导入所有命名导出,以及更多知识,参见 MDN 文档 。
至今还没有浏览器完全实现 ES2015 的所有规范。因此,无法直接在浏览器里使用 ES2015 。
那么我们该怎么做?
欢迎来到 transpiling 的世界。
现在可以按 ES2015 写代码,然后使用一个工具把它转换成 ES5,最有名的一个 transpiler 便是 Babel 。
设置 Babel 并没那么简单,需要一定的经验,这是一些新手包,拿去用吧。
此外,可以使用 Meteor ,默认支持 ES2015 了。
深入研究,可以参考以下链接: