6

ECMAScript 2015 简易教程

 3 years ago
source link: https://yanhaijing.com/javascript/2015/09/11/learn-es2015/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文最初源自es6features,你可以到github上去加星。你可以使用REPL在线预览这些特性。

ECMAScript 6 是ECMAScript标准的最新版本,于2015年6月批准通过。ES2015是对语言的一次重大更新,是自从2009年ES5标准化后的第一次重大更新。主要的JavaScript引擎正在逐步实现这些特性,点击这里查看浏览器的兼容情况。

查看ES2015 标准,了解关于ECMAScript 6语言的完整规范。

ECMAScript 6 新特性

箭头函数和静态this(Arrows and Lexical This)

箭头函数使用胖箭头(=>)语法,与C#,Java 8和CoffeeScript类似。支持表达式和语句作为函数体。不像普通函数,箭头函数的this是和文法作用域绑定的。

// 表达式作为函数体
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);

// 语句作为函数体
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v);
});

// 静态this
var bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
};

类(Classes)

ES2015中的类是对基于原型面向对象模式的一个简单语法糖。有一个单一的声明形式可以使类模式更容易使用,并鼓励互操作性。类支持基于原型的继承,超类调用(super),实例方法,静态方法和构造函数。

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)

对象字面量被扩展支持直接设置原型,简洁属性赋值和方法,超类调用。这也让对象字面量和类声明的关系更密切,并让基于对象的设计更便利。

var obj = {
    // __proto__
    __proto__: theProtoObj,
    // 下面的写法不会设置内部原型
    '__proto__': somethingElse,
    // ‘handler: handler’的简写
    handler,
    // 方法
    toString() {
     // 调用父对象的方法
     return "d " + super.toString();
    },
    // 属性名是一个表达式
    [ "prop_" + (() => 42)() ]: 42
};

__proto__属性需要原生支持,这一属性在ECMAScript前一个版本中一度被废弃。目前为止,大部分引擎支持这一属性。同时需要注意,仅仅web浏览器需要实现这一属性,在Node中现在就可使用。

模版字符串(Template Strings)

模版字符串提供构建字符串的语法糖。这类似Perl,Python等其他语言中的字符串插值。可以选择性添加一个标签,允许对字符串构建的定制化,避免注入攻击或其他需求。

// 普通字符串
`This is a pretty little template string.`

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

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 模版标签
String.raw`In ES5 "\n" is a line-feed.`

// 下面构造一个HTTP请求头,来解释差值替换和构造
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。

// 列表匹配
var [a, , b] = [1,2,3];

// 对象匹配
var { op: a, lhs: { op: b }, rhs: c }
       = getASTNode()

// 对象匹配简写
// 绑定作用域中的 `op`, `lhs` 和 `rhs`
var {op, lhs, rhs} = getASTNode()

// 函数参数
function g({name: x}) {
  console.log(x);
}
g({name: 5})

// 解构失败,赋值为undefined
var [a] = [];
a === undefined;

// 如果有默认值,则为默认值
var [a = 1] = [];
a === 1;

默认值 + Rest参数 + 扩展运算符(Default + Rest + Spread)

现在函数调用可以传递默认参数了;扩展运算符允许将数组转化为函数的参数传入;rest参数,用于获取函数的多余参数,这样就不需要使用arguments对象了。

function f(x, y=12) {
  // 如果不传递y或传递undefined,y的值为12
  return x + y;
}
f(3) == 15

function f(x, ...y) {
  // y是一个数组
  return x * y.length;
}
f(3, "hello", true) == 6

function f(x, y, z) {
  return x + y + z;
}
// 将数组扩展为三个参数
f(...[1,2,3]) == 6

Let + Const

let类似于var,但是所声明的变量,只在let命令所在的代码块内有效。const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。

function f() {
  {
    let x;
    {
      // 块作用域
      const x = "sneaky";
      // const常量重新赋值会报错
      x = "foo";
    }
    // let变量可以再次赋值
    x = "bar";
    // 在块作用域中重复声明将会报错
    let x = "inner";
  }
}

Iterator和for..of循环(Iterators + For..Of)

遍历器对象能够自定义遍历行为,这很像Java的Iterable。新增的for..of用来代替for..in。并不需要是数组,任何数据结构只要部署Iterator接口,就可以完成遍历操作。

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) {
  if (n > 1000)
    break;
  console.log(n);
}

遍历器基于弱类型接口(下面是使用TypeScript语法的展示):

interface IteratorResult {
  done: boolean;
  value: any;
}
interface Iterator {
  next(): IteratorResult;
}
interface Iterable {
  [Symbol.iterator](): Iterator
}

需要腻子脚本(Support via polyfill)

使用遍历器需要引用Babel腻子脚本

Generator 函数(Generators)

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。

注意:也可以使用‘await’——类似异步编程,参见ES7 await 提案

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) {
  // 截断1000以后的数据
  if (n > 1000)
    break;
  console.log(n);
}

generator接口原理如下(使用TypeScript语法的模拟):

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

需要腻子脚本(Support via polyfill)

为了使用Generator,需要引用Babel的腻子脚本

Unicode

ES6增强了对Unicode的支持,包括新的unicode字面量和新的正则匹配参数u模式,以及新的API来处理超过两个字节的字符。

// ES5.1中
"𠮷".length == 2

// 新的正则参数 u
"𠮷".match(/./u)[0].length == 2

// 新的表示法
"\u{20BB7}" == "𠮷" == "\uD842\uDFB7"

// 新字符串方法
"𠮷".codePointAt(0) == 0x20BB7

// for-of 
for(var c of "𠮷") {
  console.log(c);
}

模块(Modules)

语言级支持定义模块和组件。新模块的设计汇总了流行JavaScript模块加载器(AMD,CommonJS)的优点。模块的实现机制由运行时环境实现。隐式异步模型——模块加载完成之前代码不会执行。

// lib/math.js
export function sum(x, y) {
  return x + y;
}
export var pi = 3.141593;

// app.js
import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));

// otherApp.js
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

可以设置导出默认值和导出全部:

// 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";
alert("2π = " + exp(pi, e));

模块格式化

Babel可以将ES2015的模块转译成几种不同的模块格式,包括Common.js,AMD,System和UMD。你甚至可以创建自己的模块格式,更多细节可以查看模块的文档

Map + Set + WeakMap + WeakSet

专为常见算法设计的高效数据结构。WeakMaps提供对象的弱引用作为键名的索引表。

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

为了使用这些新数据结构,你必须引入babel的腻子脚本

代理(Proxies)

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

// 代理对象
var target = {};
var handler = {
  get: function (receiver, name) {
    return `Hello, ${name}!`;
  }
};

var p = new Proxy(target, handler);
p.world === "Hello, world!";
// 代理函数
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";

下面是Proxy支持的拦截操作一览:

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 :...
}

不支持的特性(Unsupported feature)

由于ES5的局限性,Proxy不能被转换和填补。点击这里查看浏览器支持情况。

Symbols

Symbol 可以作为对象的键值,是一种新的原始类型。Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。Symbol是独一无二的,但可以通过Object.getOwnPropertySymbols获取到。

(function() {

  // 块级作用域
  var key = Symbol("key");

  function MyClass(privateData) {
    this[key] = privateData;
  }

  MyClass.prototype = {
    doStuff: function() {
      ... this[key] ...
    }
  };

  // Babel不能支持下面的操作,需要原生支持
  typeof key === "symbol"
})();

var c = new MyClass("hello")
c["key"] === undefined

需要腻子脚本(不完备)(Limited support via polyfill)

需要使用Babel的腻子脚本,但无法全部支持。由于语言的限制,有些特性无法被转义和填补。更多细节请查看core.js的相关说明

继承内建父类(Subclassable Built-ins)

在ES2015中,语言内置父类如Array,Date和Dom元素能够被子类继承。

// 子类继承Array
class MyArray extends Array {
    constructor(...args) { super(...args); }
}

var arr = new MyArray();
arr[1] = 12;
arr.length == 2

部分支持(Partial support)

能够子类化的父类必须是基于类的,比如HTMLElement能被子类继承,然后由于ES5引擎的限制许多父类不能被子类化,如Date,Array和Error等。

Math + Number + String + Object APIs

ES2015扩展了很多新的API,包括核心数学函数,数组转换接口和实现对象复制功能的Object.assign。

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("*")) // 返回一个真正的数组
Array.of(1, 2, 3) // 类似new Array(...),但只有一个参数时语义不变
[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)

通过Babel的腻子脚本,能够支持大部分API,然而,由于各种原因(例如,String.prototype.normalize需要添加大量代码)并未全部支持。你可以点击这里找到更多腻子脚本。

二进制和八进制字面量(Binary and Octal Literals)

新增了二进制和八进制字面量表示法(注意和原来的ES5废弃的八进制表示法不同)。

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

仅支持字面量形式(Only supports literal form)

Babel仅能转换0o767,不支持Numver("0o767")

Promises

所谓Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。目前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)]);
})

需要使用腻子脚本(Support via polyfill)

要想使用Promises需要引用Babel腻子脚本

反射(Reflect API)

Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。

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)

Reflect API需要引用babel 腻子脚本

尾调用(Tail Calls)

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

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

// 现在的实现大部分都会移除,但在ES2015中不会
factorial(100000)

https://babeljs.io/docs/learn-es2015/

原文网址:http://yanhaijing.com/javascript/2015/09/11/learn-es2015/

微信公众号:颜海镜关注微信公众号 颜海镜微信支付二维码赞赏支持 微信扫一扫

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK