跳到主要内容

学习 ES2015

es6features

本文档最初是从 Luke Hoban 的 es6features 仓库转载的。请到 GitHub 上给他 点个赞吧!

REPL

强烈建议在 REPL 上尝试这些新功能。

简介

ECMAScript 2015 是一个 ECMAScript 标准,于 2015 年 6 月批准。

ES2015 是对 JavaScript 编程语言的重要更新,也是自 2009 年 ES5 标准化以来对该语言的首次重大更新。主流 JavaScript 引擎对这些新特性的实现 正在进行中

参考 ES2015 标准 了解 ECMAScript 2015 的完整规范。

ECMAScript 2015 特性

箭头函数与 Lexical This

箭头函数是使用 => 语法的函数简写方式。他们在语法上 与 C#、Java 8 和 CoffeeScript 中的相关功能类似。他们都支持 表达式和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.

JavaScript
// 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]

类(Class)

ES2015 中的类(class)是在基于原型的面向对象模式上简单包装的语法糖。拥有一个 单一且方便的声明形式将更易于使用,并且 鼓励混合使用。类(class)支持基于原型的继承、super 调用、 实例和静态方法以及构造函数。

JavaScript
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

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.

JavaScript
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
};
警告

The proto property requires native support, and was deprecated in previous ECMAScript versions. Most engines now support the property, but some do not. Also, note that only web browsers are required to implement it, as it's in Annex B. It is available in Node.

模板字符串

模板字符串为构造字符串提供了语法糖。这 类似于 Perl、Python 等语言中的字符串插值功能。另外, tag can be added to allow the string construction to be customized, avoiding injection attacks or constructing higher level data structures from string contents.

JavaScript
// 创建基本的字符串字面量(literal string)
`This is a pretty little template string.`

// 多行字符串
`In ES5 this is
not legal.`

// 插入变量绑定的值
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)

解构(Destructuring)允许基于模式匹配的方式进行赋值,这种模式匹配能够支持 数组(arrays)和对象(objects)。解构采取的是弱化故障的处理策略,类似于标准的对象 查找 foo["bar"],在未找到时返回值为 undefined

JavaScript
// 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

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.

JavaScript
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3) == 15
JavaScript
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true) == 6
JavaScript
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6

Let + Const

限定在块级作用域的变量定义方式。let 是新的 varconst 定义的变量只能被 赋值一次。静态限制可以防止在赋值前使用。

JavaScript
function f() {
{
let x;
{
// 因为此变量是块级作用域,因此是 ok 的
const x = "sneaky";
// 错误,定义的变量只能被赋值一次
x = "foo";
}
// 由于此变量是 `let` 定义的,因此是 ok 的
x = "bar";
// 错误,在同一块级作用域中已经被声明过了
let x = "inner";
}
}

Iterators + For..Of

Iterator 对象支持自定义迭代,就像 CLR 的 IEnumerable 或 Java 的 Iterable。利用 for..offor..in 归纳为基于自定义迭代器的迭代。 不需要实现为一个数组,并且支持像 LINQ 一样的懒设计模式(lazy design patterns)。

JavaScript
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);
}

迭代(Iteration)就是基于这些假想类型的接口(使用 TypeScript 类型的语法仅用于阐述)实现的:

interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}
通过 polyfill 提供支持

如需使用 Iterators 功能,则必须引入 Babel 的 polyfill

Generators

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).

Note: Can also be used to enable ‘await’-like async programming, see also ES7 await proposal.

JavaScript
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);
}

The generator interface is (using TypeScript type syntax for exposition only):

interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}
Support via polyfill

In order to use Generators you must include the Babel polyfill.

Comprehensions

在 Babel 6.0 中已删除

Unicode

持续完善对 Unicode 的全面支持,包括让字符串支持新的 unicode 文本(literal) 以及正则表达式(RegExp)新增的 u 模式能够支持 unicode 码位(code point),还有新的 API 用以处理 21bit 码位(code point)级别的字符串。这些新增的功能 让 JavaScript 可以构建全球化的应用程序。

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);
}

模块

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.

JavaScript
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
JavaScript
// app.js
import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));
JavaScript
// otherApp.js
import {sum, pi} from "lib/math";
console.log("2π = " + sum(pi, pi));

Some additional features include export default and export *:

JavaScript
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
JavaScript
// app.js
import exp, {pi, e} from "lib/mathplusplus";
console.log("e^π = " + exp(pi));
Module Formatters

Babel can transpile ES2015 Modules to several different formats including Common.js, AMD, System, and UMD. You can even create your own. For more details see the modules docs.

模块加载器

Not part of ES2015

This is left as implementation-defined within the ECMAScript 2015 specification. The eventual standard will be in WHATWG's Loader specification, but that is currently a work in progress. What is below is from a previous ES2015 draft.

Module loaders support:

  • Dynamic loading
  • State isolation
  • Global namespace isolation
  • Compilation hooks
  • Nested virtualization

The default module loader can be configured, and new loaders can be constructed to evaluate and load code in isolated or constrained contexts.

JavaScript
// 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
Additional polyfill needed

Since Babel defaults to using common.js modules, it does not include the polyfill for the module loader API. Get it here.

Using Module Loader

In order to use this, you'll need to tell Babel to use the system module formatter. Also be sure to check out System.js.

Map + Set + WeakMap + WeakSet

Efficient data structures for common algorithms. WeakMaps provides leak-free object-key’d side tables.

JavaScript
// 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
Support via polyfill

In order to support Maps, Sets, WeakMaps, and WeakSets in all environments you must include the Babel polyfill.

Proxy(代理)

Proxy 能够创建具有宿主对象全部可用功能的对象。 可用于拦截、对象虚拟化 日志/分析等。

JavaScript
// 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!";
JavaScript
// 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";

所有运行时级别的元数据操作都提供了 trap(陷阱)功能:

JavaScript
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 功能无法进行代码转换或使用 polyfill。请参阅 各 JavaScript 引擎 的支持情况。

Symbols

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.

JavaScript
(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
Limited support via polyfill

Limited support requires the Babel polyfill. Due to language limitations, some features can't be transpiled or polyfilled. See core.js's caveats section for more details.

可子类话的内置对象

在 ES2015 中,内置对象 ArrayDate 以及 DOM Element 可以子类化。

JavaScript
// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(...args); }
}

var arr = new MyArray();
arr[1] = 12;
arr.length == 2
部分支持

内置对象是否可子类化要具体情况具体分析,例如 HTMLElement 可以 被子类化,而例如 DateArrayError 由于受 ES5 引擎的限制 不能 被子类化。

Math + Number + String + Object APIs

Many new library additions, including core Math libraries, Array conversion helpers, and Object.assign for copying.

JavaScript
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) })
Limited support from polyfill

Most of these APIs are supported by the Babel polyfill. However, certain features are omitted for various reasons (e.g. String.prototype.normalize needs a lot of additional code to support). You can find more polyfills here.

二进制和八进制字面量

增加了对二进制 (b) 和八进制 (o) 字面量的支持。

JavaScript
0b111110111 === 503 // true
0o767 === 503 // true
仅支持字面量(literal)形式

Babel 只能转换 0o767 而不能转换 Number("0o767")

Promises

Promises 是一个异步编程用的工具库。Promises 是第一种 对将来可以使用的值的类(class)的表示形式。Promises 被 用在大量已有的 JavaScript 工具库中。

JavaScript
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)]);
})
通过 polyfill 提供支持

为了支持 Promise 功能,你必须引入 Babel 的 polyfill

反射 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.

JavaScript
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
Support via polyfill

In order to use the Reflect API you must include the Babel polyfill.

尾部调用(Tail Calls)

尾部调用不会让栈(stack)无限制地增长。这让 递归算法在面对不做限制的输入时能够安全执行。

JavaScript
function factorial(n, acc = 1) {
"use strict";
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}

// 在如今的大部分实现中都将引起栈溢出(Stack overflow),
// 但是在 ES2015 中处理任意数量的输入都是安全的
factorial(100000)
暂时从 Babel 6 中删除

由于支持全局尾部调用的复杂性和对性能的影响, 只支持显式自引用方式的尾部递归(explicit self referencing tail recursion)。 由于其它 bug 的影响而将其删除,并将重新实现。