学习 ES2015
这份文档最初摘自 Luke Hoban 出色的 es6features 仓库。去 GitHub 点个星吧!
一定要在在线 REPL中试用这些功能。
介绍
🌐 Introduction
ECMAScript 2015 是一个 ECMAScript 标准,于 2015 年 6 月通过。
ES2015 是对语言的一次重大更新,也是自 2009 年 ES5 标准化以来的第一次重大更新。主要 JavaScript 引擎中这些特性的实现正在进行中。
🌐 ES2015 is a significant update to the language, and the first major update to the language since ES5 was standardized in 2009. Implementation of these features in major JavaScript engines is underway now.
请查看 ES2015 标准 以获取 ECMAScript 2015 语言的完整规范。
🌐 See the ES2015 standard for full specification of the ECMAScript 2015 language.
ECMAScript 2015 特性
🌐 ECMAScript 2015 Features
箭头和词法 This
🌐 Arrows and Lexical This
箭头函数是一种使用 => 语法的函数简写。它们在语法上类似于 C#、Java 8 和 CoffeeScript 中的相关特性。箭头函数支持表达式和语句体。与普通函数不同,箭头函数共享它们所在代码的同一个词法 this。如果箭头函数在另一个函数内部,它会共享父函数的“arguments”变量。
🌐 Arrows are a function shorthand using the => syntax. They are syntactically
similar to the related feature in C#, Java 8 and CoffeeScript. They support
both expression and statement bodies. Unlike functions, arrows share the same
lexical this as their surrounding code. If an arrow is inside another function,
it shares the "arguments" variable of its parent function.
// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
// Statement bodies
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});
// Lexical this
var bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
};
// Lexical arguments
function square() {
let example = () => {
let numbers = [];
for (let number of arguments) {
numbers.push(number * number);
}
return numbers;
};
return example();
}
square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441]
类
🌐 Classes
ES2015的类是基于原型的面向对象模式的语法糖。拥有一个方便的声明式形式让类模式更容易使用,也鼓励互操作性。类支持基于原型的继承、super调用、实例和静态方法以及构造函数。
🌐 ES2015 classes are syntactic sugar over the prototype-based OO pattern. Having a single convenient declarative form makes class patterns easier to use, and encourages interoperability. Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);
this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
//...
}
update(camera) {
//...
super.update();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}
增强的对象字面量
🌐 Enhanced Object Literals
对象字面量被扩展,以支持在构造时设置原型、foo: foo 赋值的简写、定义方法以及调用 super。这些改进也让对象字面量和类声明更加接近,并让基于对象的设计能够享受一些相同的便利。
🌐 Object literals are extended to support setting the prototype at construction,
shorthand for foo: foo assignments, defining methods and making super calls.
Together, these also bring object literals and class declarations closer
together, and let object-based design benefit from some of the same
conveniences.
var obj = {
// Sets the prototype. "__proto__" or '__proto__' would also work.
__proto__: theProtoObj,
// Computed property name does not set prototype or trigger early error for
// duplicate __proto__ properties.
['__proto__']: somethingElse,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ "prop_" + (() => 42)() ]: 42
};
模板字符串
🌐 Template Strings
模板字符串为构建字符串提供了语法糖。这类似于 Perl、Python 等语言的字符串插值功能。可以选择添加一个标签,以允许自定义字符串构建,从而避免注入攻击或从字符串内容构建更高级的数据结构。
🌐 Template strings provide syntactic sugar for constructing strings. This is similar to string interpolation features in Perl, Python and more. Optionally, a tag can be added to allow the string construction to be customized, avoiding injection attacks or constructing higher level data structures from string contents.
// Basic literal string creation
`This is a pretty little template string.`
// Multiline strings
`In ES5 this is
not legal.`
// Interpolate variable bindings
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// Unescaped template strings
String.raw`In ES5 "\n" is a line-feed.`
// Construct an HTTP request prefix is used to interpret the replacements and construction
GET`http://foo.org/bar?a=${a}&b=${b}
Content-Type: application/json
X-Credentials: ${credentials}
{ "foo": ${foo},
"bar": ${bar}}`(myOnReadyStateChangeHandler);
解构
🌐 Destructuring
解构允许使用模式匹配进行绑定,支持匹配数组和对象。解构是容错的,类似于标准对象查找 foo["bar"],在未找到时生成 undefined 值。
🌐 Destructuring allows binding using pattern matching, with support for matching
arrays and objects. Destructuring is fail-soft, similar to standard object
lookup foo["bar"], producing undefined values when not found.
// list matching
var [a, ,b] = [1,2,3];
a === 1;
b === 3;
// object matching
var { op: a, lhs: { op: b }, rhs: c }
= getASTNode()
// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()
// Can be used in parameter position
function g({name: x}) {
console.log(x);
}
g({name: 5})
// Fail-soft destructuring
var [a] = [];
a === undefined;
// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;
// Destructuring + defaults arguments
function r({x, y, w = 10, h = 10}) {
return x + y + w + h;
}
r({x:1, y:2}) === 23
默认 + 剩余 + 展开
🌐 Default + Rest + Spread
被调用者评估的默认参数值。在函数调用中将数组变为连续的参数。将后续参数绑定到一个数组。Rest 取代了 arguments 的需求,更直接地解决了常见情况。
🌐 Callee-evaluated default parameter values. Turn an array into consecutive
arguments in a function call. Bind trailing parameters to an array. Rest
replaces the need for arguments and addresses common cases more directly.
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3) == 15
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true) == 6
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6
Let + Const
块级作用域绑定构造。let 是新的 var。const 是单次赋值的。静态限制禁止在赋值前使用。
🌐 Block-scoped binding constructs. let is the new var. const is
single-assignment. Static restrictions prevent use before assignment.
function f() {
{
let x;
{
// this is ok since it's a block scoped name
const x = "sneaky";
// error, was just defined with `const` above
x = "foo";
}
// this is ok since it was declared with `let`
x = "bar";
// error, already declared above in this block
let x = "inner";
}
}
迭代器 + For..Of
🌐 Iterators + For..Of
迭代器对象支持自定义迭代,就像 CLR 的 IEnumerable 或 Java 的 Iterable。将 for..in 泛化为基于自定义迭代器的迭代,用 for..of 实现。无需将数据变成数组,从而支持像 LINQ 这样的惰性设计模式。
🌐 Iterator objects enable custom iteration like CLR IEnumerable or Java
Iterable. Generalize for..in to custom iterator-based iteration with
for..of. Don’t require realizing an array, enabling lazy design patterns like
LINQ.
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur }
}
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
迭代是基于这些鸭子类型接口(仅使用TypeScript 类型语法进行说明):
🌐 Iteration is based on these duck-typed interfaces (using TypeScript type syntax for exposition only):
interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}
要使用迭代器,你必须包含 Babel polyfill。
生成器
🌐 Generators
生成器通过 function* 和 yield 简化了迭代器的编写。以 function* 声明的函数会返回一个 Generator 实例。生成器是迭代器的子类型,包含额外的 next 和 throw。这些让值可以流回生成器,因此 yield 是一种返回值(或抛出异常)的表达式形式。
🌐 Generators simplify iterator-authoring using function* and yield. A function
declared as function* returns a Generator instance. Generators are subtypes of
iterators which include additional next and throw. These enable values to
flow back into the generator, so yield is an expression form which returns a
value (or throws).
注意:也可以用来启用类似‘await’的异步编程,也可以看看 ES7 await 提案。
🌐 Note: Can also be used to enable ‘await’-like async programming, see also ES7 await proposal.
var fibonacci = {
[Symbol.iterator]: function*() {
var pre = 0, cur = 1;
for (;;) {
var temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
生成器接口是(仅用于说明,使用 TypeScript 类型语法):
🌐 The generator interface is (using TypeScript type syntax for exposition only):
interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}
要使用生成器,你必须包含 Babel polyfill。
推导式
🌐 Comprehensions
在 Babel 6.0 中移除
🌐 Removed in Babel 6.0
Unicode
支持完整 Unicode 的非破坏性新增功能,包括字符串中新增加的 Unicode 字面量形式,以及处理代码点的新 RegExp u 模式,同时还有新的 API 可在 21 位代码点级别处理字符串。这些新增功能支持在 JavaScript 中构建全球化应用。
🌐 Non-breaking additions to support full Unicode, including new unicode literal
form in strings and new RegExp u mode to handle code points, as well as new
APIs to process strings at the 21bit code points level. These additions support
building global apps in JavaScript.
// same as ES5.1
"𠮷".length == 2
// new RegExp behaviour, opt-in ‘u’
"𠮷".match(/./u)[0].length == 2
// new form
"\u{20BB7}" == "𠮷"
"𠮷" == "\uD842\uDFB7"
// new String ops
"𠮷".codePointAt(0) == 0x20BB7
// for-of iterates code points
for(var c of "𠮷") {
console.log(c);
}
模块
🌐 Modules
为组件定义提供语言级别的模块支持。将流行的 JavaScript 模块加载器(AMD、CommonJS)的模式进行了规范化。运行时行为由宿主定义的默认加载器决定。隐式异步模型——在请求的模块可用且处理完成之前,代码不会执行。
🌐 Language-level support for modules for component definition. Codifies patterns from popular JavaScript module loaders (AMD, CommonJS). Runtime behaviour defined by a host-defined default loader. Implicitly async model – no code executes until requested modules are available and processed.
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));
// otherApp.js
import {sum, pi} from "lib/math";
console.log("2π = " + sum(pi, pi));
一些额外的功能包括 export default 和 export *:
🌐 Some additional features include export default and export *:
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
// app.js
import exp, {pi, e} from "lib/mathplusplus";
console.log("e^π = " + exp(pi));
Babel 可以将 ES2015 模块转译成多种不同的格式,包括 Common.js、AMD、System 和 UMD。你甚至可以自己创建一个。更多详情请参见 模块文档。
模块加载器
🌐 Module Loaders
在 ECMAScript 2015 规范中,这部分被留作实现自定义。最终标准将会出现在 WHATWG 的 Loader 规范中,但目前它仍在开发中。下面的内容来源于之前的 ES2015 草案。
模块加载器支持:
🌐 Module loaders support:
- 动态加载
- 状态隔离
- 全局命名空间隔离
- 编译钩子
- 嵌套虚拟化
默认的模块加载器可以进行配置,也可以构建新的加载器在隔离或受限的环境中评估和加载代码。
🌐 The default module loader can be configured, and new loaders can be constructed to evaluate and load code in isolated or constrained contexts.
// Dynamic loading – ‘System’ is default loader
System.import("lib/math").then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});
// Create execution sandboxes – new Loaders
var loader = new Loader({
global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log(\"hello world!\");");
// Directly manipulate module cache
System.get("jquery");
System.set("jquery", Module({$: $})); // WARNING: not yet finalized
由于 Babel 默认使用 common.js 模块,它并不包含模块加载器 API 的 polyfill。可以在 这里获取。
为了使用这个,你需要告诉 Babel 使用 system 模块格式化器。也别忘了看看 System.js。
Map + Set + WeakMap + WeakSet
常用算法的高效数据结构。WeakMap 提供无泄漏的对象键副表。
🌐 Efficient data structures for common algorithms. WeakMaps provides leak-free object-key’d side tables.
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
为了在所有环境中支持 Maps、Sets、WeakMaps 和 WeakSets,你必须包含 Babel polyfill。
代理
🌐 Proxies
代理允许创建具有宿主对象全部行为的对象。可以用于拦截、对象虚拟化、日志记录/性能分析等。
🌐 Proxies enable creation of objects with the full range of behaviors available to host objects. Can be used for interception, object virtualization, logging/profiling, etc.
// Proxying a normal object
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};
var p = new Proxy(target, handler);
p.world === "Hello, world!";
// Proxying a function object
var target = function () { return "I am the target"; };
var handler = {
apply: function (receiver, ...args) {
return "I am the proxy";
}
};
var p = new Proxy(target, handler);
p() === "I am the proxy";
所有运行时级元操作都有可用的捕获:
🌐 There are traps available for all of the runtime-level meta-operations:
var handler =
{
// target.prop
get: ...,
// target.prop = value
set: ...,
// 'prop' in target
has: ...,
// delete target.prop
deleteProperty: ...,
// target(...args)
apply: ...,
// new target(...args)
construct: ...,
// Object.getOwnPropertyDescriptor(target, 'prop')
getOwnPropertyDescriptor: ...,
// Object.defineProperty(target, 'prop', descriptor)
defineProperty: ...,
// Object.getPrototypeOf(target), Reflect.getPrototypeOf(target),
// target.__proto__, object.isPrototypeOf(target), object instanceof target
getPrototypeOf: ...,
// Object.setPrototypeOf(target), Reflect.setPrototypeOf(target)
setPrototypeOf: ...,
// Object.keys(target)
ownKeys: ...,
// Object.preventExtensions(target)
preventExtensions: ...,
// Object.isExtensible(target)
isExtensible :...
}
由于 ES5 的限制,Proxy 无法被转译或填充。请参阅 各种 JavaScript 引擎的支持情况。
符号
🌐 Symbols
符号可以实现对象状态的访问控制。符号允许属性使用 string(如 ES5 中)或 symbol 作为键。符号是一种新的原始类型。可选的 name 参数用于调试,但不属于身份的一部分。符号是唯一的(像 gensym 一样),但并非私有,因为它们可以通过诸如 Object.getOwnPropertySymbols 的反射功能访问。
🌐 Symbols enable access control for object state. Symbols allow properties to be
keyed by either string (as in ES5) or symbol. Symbols are a new primitive
type. Optional name parameter used in debugging - but is not part of identity.
Symbols are unique (like gensym), but not private since they are exposed via
reflection features like Object.getOwnPropertySymbols.
(function() {
// module scoped symbol
var key = Symbol("key");
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};
// Limited support from Babel, full support requires native implementation.
typeof key === "symbol"
})();
var c = new MyClass("hello")
c["key"] === undefined
可子类化的内置函数
🌐 Subclassable Built-ins
在 ES2015 中,像 Array、Date 和 DOM Element 这样的内置对象可以被继承。
🌐 In ES2015, built-ins like Array, Date and DOM Elements can be subclassed.
// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(...args); }
}
var arr = new MyArray();
arr[1] = 12;
arr.length == 2
内置类的可继承性应该根据具体情况评估,因为像 HTMLElement 这样的类 可以 被继承,而像 Date、Array 和 Error 这样的类由于 ES5 引擎的限制 不能 被继承。
数学 + 数字 + 字符串 + 对象 API
🌐 Math + Number + String + Object APIs
许多新的库新增功能,包括核心数学库、数组转换助手以及用于复制的 Object.assign。
🌐 Many new library additions, including core Math libraries, Array conversion helpers, and Object.assign for copying.
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll("*")) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
大多数这些 API 都由 Babel polyfill 支持。不过,某些功能因为各种原因被省略了(例如 String.prototype.normalize 需要很多额外的代码来支持)。你可以在这里 找到更多 polyfills。
二进制和八进制字面量
🌐 Binary and Octal Literals
新增了两种数字字面量形式,分别用于二进制(b)和八进制(o)。
🌐 Two new numeric literal forms are added for binary (b) and octal (o).
0b111110111 === 503 // true
0o767 === 503 // true
Babel 只能转换 0o767,而不能转换 Number("0o767")。
Promise
🌐 Promises
Promise 是用于异步编程的库。Promise 是对未来可能可用的值的一级表示。Promise 被用于许多现有的 JavaScript 库中。
🌐 Promises are a library for asynchronous programming. Promises are a first class representation of a value that may be made available in the future. Promises are used in many existing JavaScript libraries.
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}
var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
})
为了支持 Promises,你必须包含 Babel polyfill。
反射 API
🌐 Reflect API
完整的反射 API,公开了对对象的运行时级元操作。这实际上是 Proxy API 的反向操作,并且允许调用与代理陷阱相同的元操作。对实现代理尤其有用。
🌐 Full reflection API exposing the runtime-level meta-operations on objects. This is effectively the inverse of the Proxy API, and allows making calls corresponding to the same meta-operations as the proxy traps. Especially useful for implementing proxies.
var O = {a: 1};
Object.defineProperty(O, 'b', {value: 2});
O[Symbol('c')] = 3;
Reflect.ownKeys(O); // ['a', 'b', Symbol(c)]
function C(a, b){
this.c = a + b;
}
var instance = Reflect.construct(C, [20, 22]);
instance.c; // 42
为了使用 Reflect API,你必须包含 Babel polyfill。
尾调用
🌐 Tail Calls
尾递归调用保证不会无限增加栈。让递归算法在面对无限输入时也安全。
🌐 Calls in tail-position are guaranteed to not grow the stack unboundedly. Makes recursive algorithms safe in the face of unbounded inputs.
function factorial(n, acc = 1) {
"use strict";
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
// Stack overflow in most implementations today,
// but safe on arbitrary inputs in ES2015
factorial(100000)
由于全局支持尾调用的复杂性和性能影响,只支持显式自引用的尾递归。因为其他bug被移除了,将会重新实现。