ECMAScript 2015 簡易教程

jopen 9年前發布 | 119K 次閱讀 JavaScript開發 ECMAScript

背景

本文最初源自es6features,你可以到github上去加星。你可以使用REPL在線預覽這些特性。

簡介

ECMAScript 6 是ECMAScript標準的最新版本,于2015年6月批準通過。ES2015是對語言的一次重大更新,是自從2009年ES5標準化后的第一次重大更新。主要的JavaScript引擎正在逐步實現這些特性,點擊這里查看瀏覽器的兼容情況。

</blockquote>

查看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)); } };</pre>

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

增強對象字面量(Enhanced Object Literals)

對象字面量被擴展支持直接設置原型,簡潔屬性賦值和方法,超類調用。這也讓對象字面量和類聲明的關系更密切,并讓基于對象的設計更便利。

var obj = {
    // proto
    proto: theProtoObj,
    // 下面的寫法不會設置內部原型
    'proto': somethingElse,
    // ‘handler: handler’的簡寫
    handler,
    // 方法
    toString() {
     // 調用父對象的方法
     return "d " + super.toString();
    },
    // 屬性名是一個表達式

[ "prop_" + (() => 42)() ]: 42

};</pre>

__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.rawIn ES5 "\n" is a line-feed.

// 下面構造一個HTTP請求頭,來解釋差值替換和構造 GEThttp://foo.org/bar?a=${a}&amp;b=${b} Content-Type: application/json X-Credentials: ${credentials} { "foo": ${foo}, "bar": ${bar}}(myOnReadyStateChangeHandler);</pre>

解構(Destructuring)

按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構。解構失敗是靜默的,類似標準的對象屬性查找foo[“bar”],找不到值則為undefined。

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

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

// 對象匹配簡寫 // 綁定作用域中的 op, lhsrhs 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;</pre>

默認值 + 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</pre>

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

遍歷器基于弱類型接口(下面是使用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); }</pre>

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

模塊(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));</pre>

可以設置導出默認值和導出全部:

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

模塊格式化

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

需要使用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";</pre>

下面是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</pre>

需要膩子腳本(不完備)(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</pre>

部分支持(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) })</pre>

需要使用膩子腳本(不能完全模擬)(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)]); })</pre>

需要使用膩子腳本(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</pre>

需要使用膩子腳本(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)</pre>

原文鏈接

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

更多資料

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!