# 学习 ES2015

es6功能

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

REPL

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

# 简介 (opens new window)

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

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

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

# ECMAScript 2015 特性 (opens new window)

# 箭头函数与 Lexical This

箭头函数是使用 =>语法的函数简写方式。他们在语法上 与 C#、Java 8 和 CoffeeScript 中的相关功能类似。他们都支持 表达式和both expression and statement bodies. Unlike functions, arrows share the same lexical thisas 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) (opens new window)

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

# 增强的对象字面量 (opens new window)

对象文字被扩展以支持在构造时设置原型、foo: foo赋值的简写、定义方法和进行超级调用。同时,这些也使对象文字和类声明更紧密地结合在一起,并让基于对象的设计受益于一些相同的便利。

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

警告 该proto属性需要本机支持,并且在以前的 ECMAScript 版本中已弃用。大多数引擎现在都支持该属性,但有些引擎不 (opens new window) 支持。另外,请注意只有Web 浏览器 (opens new window) 才能实现它,因为它在附件 B (opens new window) 中。它在节点中可用。

# 模板字符串 (opens new window)

模板字符串为构造字符串提供了语法糖。这 类似于 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) (opens new window)

解构(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

# 默认 + 休息 +价差 (opens new window)

被调用方评估的默认参数值。在函数调用中将数组转换为连续参数。将尾随参数绑定到数组。Rest 取代了对常见情况的需求arguments并更直接地解决了常见问题。

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

# 让+常量 (opens new window)

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

JavaScript

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

# 迭代器 + 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

# 发电机

function*生成器使用和简化了迭代器创作yield。声明为 function* 的函数返回一个 Generator 实例。生成器是迭代器的子类型,其中包括附加的nextthrow。这些使值能够流回生成器,yield返回值(或抛出)的表达式形式也是如此。

注意:也可用于启用类似“等待”的异步编程,另请参阅 ES7await提案 (opens new window)

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

生成器接口是(仅使用TypeScript (opens new window) 类型语法进行说明):

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

通过 polyfill 支持 为了使用生成器,你必须包含 Babel polyfill

# 感悟

在 Babel 6.0 中已删除

# 统一码

持续完善对 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);
}

# 模块 (opens new window)

对组件定义模块的语言级支持。将流行的 JavaScript 模块加载器(AMD、CommonJS)的模式编码。由主机定义的默认加载程序定义的运行时行为。隐式异步模型——在请求的模块可用和处理之前不执行任何代码。

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

一些附加功能export default包括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));

模块格式化程序 Babel 可以将 ES2015 模块转换为多种不同的格式,包括 Common.js、AMD、System 和 UMD。您甚至可以创建自己的。有关详细信息,请参阅模块文档 (opens new window)

# 模块加载器 (opens new window)

不是 ES2015 的一部分 这在 ECMAScript 2015 规范中保留为实现定义。最终的标准将在 WHATWG 的Loader 规范 (opens new window) 中,但目前正在进行中。以下内容来自之前的 ES2015 草案。

模块加载器支持:

  • 动态加载
  • 状态隔离
  • 全局命名空间隔离
  • 编译钩子
  • 嵌套虚拟化

可以配置默认模块加载器,并且可以构建新的加载器以在隔离或受限的上下文中评估和加载代码。

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

需要额外的 polyfill 由于 Babel 默认使用 common.js 模块,因此它不包含模块加载器 API 的 polyfill。在这里 (opens new window) 得到它。

使用模块加载器 为了使用它,你需要告诉 Babel 使用system模块格式化程序。另外一定要检查System.js (opens new window)

# Map + Set + WeakMap + WeakSet

通用算法的高效数据结构。WeakMaps 提供无泄漏的对象键边表。

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

通过 polyfill 支持 为了在所有环境中支持 Maps、Sets、WeakMaps 和 WeakSets,你必须包含 Babel polyfill

# Proxy(代理) (opens new window)

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: ...,
  // for (let i in target) {}
  enumerate: ...,
  // Object.keys(target)
  ownKeys: ...,
  // Object.preventExtensions(target)
  preventExtensions: ...,
  // Object.isExtensible(target)
  isExtensible :...
}

不支持的功能 受 ES5 所限,Proxy 功能无法进行代码转换或使用 polyfill。请参阅 各 JavaScript 引擎 的支持情况。

# 符号

符号启用对象状态的访问控制。符号允许属性通过string(如在 ES5 中)或symbol. 符号是一种新的原始类型。调试中使用的可选name参数 - 但不是身份的一部分。符号是唯一的(如 gensym),但不是私有的,因为它们通过像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

通过 polyfill 提供有限的支持 有限的支持需要 Babel polyfill 。由于语言限制,某些功能无法转译或填充。有关详细信息,请参阅 core.js 的警告部分。 (opens new window)

# 可子类话的内置对象 (opens new window)

在 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 引擎的限制 不能被子类化。

# 数学 + 数字 + 字符串 + 对象API (opens new window)

许多新的库添加,包括核心数学库、数组转换助手和用于复制的 Object.assign。

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

polyfill 的有限支持 Babel polyfill 支持这些 API 中的大部分。String.prototype.normalize然而,由于各种原因(例如需要大量额外的代码来支持),某些功能被省略了。您可以在此处 (opens new window) 找到更多 polyfill 。

# 二进制和八进制字面量 (opens new window)

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

JavaScript

0b111110111 === 503 // true
0o767 === 503 // true

仅支持字面量(literal)形式 Babel 只能转换 0o767而不能转换 Number("0o767")

# 承诺

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

全反射 API 公开对象的运行时级元操作。这实际上是代理 API 的逆向,并允许进行与代理陷阱相同的元操作对应的调用。对于实现代理特别有用。

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

通过 polyfill 支持 为了使用 Reflect API,你必须包含 Babel polyfill

# 尾部调用(Tail Calls) (opens new window)

尾部调用不会让栈(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 的影响而将其删除,并将重新实现。

Last Updated: 6/14/2023, 8:56:23 AM