1. 程式人生 > >你該知道的ES7和ES8新特性

你該知道的ES7和ES8新特性

ES7就是ES2016規範,ES8就是ES2017規範。

本文主要內容轉載自部落格園大轉轉FE的10分鐘學會ES7+ES8,但是這篇文章沒有講ES8的裝飾器(Decorator),所以另轉載阮一峰老師的ECMAScript6標準入門中關於裝飾器的部分內容。

----------------------------------------(10分鐘學會ES7+ES8)--------------------------------------------

撰文為何

身為一個前端開發者,ECMAScript(以下簡稱ES)早已廣泛應用在我們的工作當中。瞭解ECMA機構流程的人應該知道,標準委員會會在每年的6月份正式釋出一次規範的修訂,而這次的釋出也將作為當年的正式版本。以後的改動,都會基於上一版本進行修改。所以,我們這次就基於ES6的版本對ES7、ES8版本的新增以及修改內容,做一次簡要的總結,方便我們快速開發。

ES7新特性

ES7在ES6的基礎上添加了三項內容:求冪運算子(**)、Array.prototype.includes()方法、函式作用域中嚴格模式的變更。

Array.prototype.includes()方法

includes()的作用,是查詢一個值在不在數組裡,若在,則返回true,反之返回false。 基本用法:

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false

Array.prototype.includes()方法接收兩個引數:要搜尋的值和搜尋的開始索引。當第二個引數被傳入時,該方法會從索引處開始往後搜尋(預設索引值為0)。若搜尋值在陣列中存在則返回true

,否則返回false。 且看下面示例:

['a', 'b', 'c', 'd'].includes('b')         // true
['a', 'b', 'c', 'd'].includes('b', 1)      // true
['a', 'b', 'c', 'd'].includes('b', 2)      // false

那麼,我們會聯想到ES6裡陣列的另一個方法indexOf,下面的示例程式碼是等效的:

['a', 'b', 'c'].includes('a')          //true
['a', 'b', 'c'].indexOf('a') > -1      //true

此時,就有必要來比較下兩者的優缺點和使用場景了。

  • 簡便性

從這一點上來說,includes略勝一籌。熟悉indexOf的同學都知道,indexOf返回的是某個元素在陣列中的下標值,若想判斷某個元素是否在數組裡,我們還需要做額外的處理,即判斷該返回值是否>-1。而includes則不用,它直接返回的便是Boolean型的結果。

  • 精確性

兩者使用的都是 === 操作符來做值的比較。但是includes()方法有一點不同,兩個NaN被認為是相等的,即使在NaN === NaN結果是false的情況下。這一點和indexOf()的行為不同,indexOf()嚴格使用===判斷。請看下面示例程式碼:

let demo = [1, NaN, 2, 3]

demo.indexOf(NaN)        //-1
demo.includes(NaN)       //true

上述程式碼中,indexOf()方法返回-1,即使NaN存在於陣列中,而includes()則返回了true。

提示:由於它對NaN的處理方式與indexOf不同,假如你只想知道某個值是否在陣列中而並不關心它的索引位置,建議使用includes()。如果你想獲取一個值在陣列中的位置,那麼你只能使用indexOf方法。

includes()還有一個怪異的點需要指出,在判斷 +0 與 -0 時,被認為是相同的。

[1, +0, 3, 4].includes(-0)    //true
[1, +0, 3, 4].indexOf(-0)     //1

在這一點上,indexOf()includes()的處理結果是一樣的,前者同樣會返回 +0 的索引值。

注意:在這裡,需要注意一點,includes()只能判斷簡單型別的資料,對於複雜型別的資料,比如物件型別的陣列,二維陣列,這些,是無法判斷的。

求冪運算子(**)

基本用法


3 ** 2           // 9

效果同:


Math.pow(3, 2)   // 9

** 是一個用於求冪的中綴運算元,比較可知,中綴符號比函式符號更簡潔,這也使得它更為可取。 下面讓我們擴充套件下思路,既然說**是一個運算子,那麼它就應該能滿足類似加等的操作,我們姑且稱之為冪等,例如下面的例子,a的值依然是9:

let a = 3
a **= 2
// 9

對比下其他語言的指數運算子:

  • Python: x ** y
  • CoffeeScript: x ** y
  • F#: x ** y
  • Ruby: x ** y
  • Perl: x ** y
  • Lua, Basic, MATLAB: x ^ y

不難發現,ES的這個新特性是從其他語言(Python,Ruby等)模仿而來的。

ES8新特性

非同步函式(Async functions)

為什麼要引入async

眾所周知,JavaScript語言的執行環境是“單執行緒”的,那麼非同步程式設計對JavaScript語言來說就顯得尤為重要。以前我們大多數的做法是使用回撥函式來實現JavaScript語言的非同步程式設計。回撥函式本身沒有問題,但如果出現多個回撥函式巢狀,例如:進入某個頁面,需要先登入,拿到使用者資訊之後,調取使用者商品資訊,程式碼如下:

this.$http.jsonp('/login', (res) => {
  this.$http.jsonp('/getInfo', (info) => {
    // do something
  })
})

假如上面還有更多的請求操作,就會出現多重巢狀。程式碼很快就會亂成一團,這種情況就被稱為“回撥函式地獄”(callback hell)。

於是,我們提出了Promise,它將回調函式的巢狀,改成了鏈式呼叫。寫法如下:

var promise = new Promise((resolve, reject) => {
  this.login(resolve)
})
.then(() => this.getInfo())
.catch(() => { console.log("Error") })

從上面可以看出,Promise的寫法只是回撥函式的改進,使用then方法,只是讓非同步任務的兩段執行更清楚而已。Promise的最大問題是程式碼冗餘,請求任務多時,一堆的then,也使得原來的語義變得很不清楚。此時我們引入了另外一種非同步程式設計的機制:Generator。

Generator 函式是一個普通函式,但是有兩個特徵。一是,function關鍵字與函式名之間有一個星號;二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)。一個簡單的例子用來說明它的用法:

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator(); 

上面程式碼定義了一個 Generator 函式helloWorldGenerator,它內部有兩個yield表示式(hello和world),即該函式有三個狀態:hello,world 和 return 語句(結束執行)。Generator 函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫 Generator 函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件,必須呼叫遍歷器物件的next方法,使得指標移向下一個狀態。也就是說,每次呼叫next方法,內部指標就從函式頭部或上一次停下來的地方開始執行,直到遇到下一個yield表示式(或return語句)為止。換言之,Generator 函式是分段執行的,yield表示式是暫停執行的標記,而next方法可以恢復執行。上述程式碼分步執行如下:

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true } 

Generator函式的機制更符合我們理解的非同步程式設計思想。

使用者登入的例子,我們用Generator來寫,如下:

var gen = function* () {
  const f1 = yield this.login()
  const f2 = yield this.getInfo()
};

雖然Generator將非同步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)。此時,我們便希望能出現一種能自動執行Generator函式的方法。我們的主角來了:async/await。

ES8引入了async函式,使得非同步操作變得更加方便。簡單說來,它就是Generator函式的語法糖。

async function asyncFunc(params) {
  const result1 = await this.login()
  const result2 = await this.getInfo()
}

是不是更加簡潔易懂呢?

變體

非同步函式存在以下四種使用形式:

  • 函式宣告: async function foo() {}
  • 函式表示式: const foo = async function() {}
  • 物件的方式: let obj = { async foo() {} }
  • 箭頭函式: const foo = async () => {}

常見用法彙總

處理單個非同步結果:

async function asyncFunc() {
  const result = await otherAsyncFunc();
  console.log(result);
}

順序處理多個非同步結果:

async function asyncFunc() {
  const result1 = await otherAsyncFunc1();
  console.log(result1);
  const result2 = await otherAsyncFunc2();
  console.log(result2);
}

並行處理多個非同步結果:

async function asyncFunc() {
  const [result1, result2] = await Promise.all([
    otherAsyncFunc1(),
    otherAsyncFunc2()
  ]);
  console.log(result1, result2);
}

處理錯誤:

async function asyncFunc() {
  try {
    await otherAsyncFunc();
  } catch (err) {
    console.error(err);
  }
}

Object.entries()和Object.values()

Object.entries()

如果一個物件是具有鍵值對的資料結構,則每一個鍵值對都將會編譯成一個具有兩個元素的陣列,這些陣列最終會放到一個數組中,返回一個二維陣列。簡言之,該方法會將某個物件的可列舉屬性與值按照二維陣列的方式返回。若目標物件是陣列時,則會將陣列的下標作為鍵值返回。例如:

Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.entries([1, 2])                //[['0', 1], ['1', 2]]

注意:鍵值對中,如果鍵的值是Symbol,編譯時將會被忽略。例如:


Object.entries({ [Symbol()]: 1, two: 2 })       //[['two', 2]]

Object.entries()返回的陣列的順序與for-in迴圈保持一致,即如果物件的key值是數字,則返回值會對key值進行排序,返回的是排序後的結果。例如:

Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]

使用Object.entries(),我們還可以進行物件屬性的遍歷。例如:

let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
  console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}

//輸出結果如下:
'one': 1
'two': 2

Object.values()

它的工作原理跟Object.entries()很像,顧名思義,它只返回自己的鍵值對中屬性的值。它返回的陣列順序,也跟Object.entries()保持一致。

Object.values({ one: 1, two: 2 })            //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' })    //['c', 'a', 'b']

字串填充:padStart和padEnd

ES8提供了新的字串方法-padStart和padEnd。padStart函式通過填充字串的首部來保證字串達到固定的長度,反之,padEnd是填充字串的尾部來保證字串的長度的。該方法提供了兩個引數:字串目標長度和填充欄位,其中第二個引數可以不填,預設情況下使用空格填充。

'Vue'.padStart(10)           //'       Vue'
'React'.padStart(10)         //'     React'
'JavaScript'.padStart(10)    //'JavaScript'

可以看出,多個數據如果都採用同樣長度的padStart,相當於將呈現內容右對齊。

上面示例中我們只定義了第一個引數,那麼我們現在來看看第二個引數,我們可以指定字串來代替空字串。

'Vue'.padStart(10, '_*')           //'_*_*_*_Vue'
'React'.padStart(10, 'Hello')      //'HelloReact'
'JavaScript'.padStart(10, 'Hi')    //'JavaScript'
'JavaScript'.padStart(8, 'Hi')     //'JavaScript'

從上面結果來看,填充函式只有在字元長度小於目標長度時才有效,若字元長度已經等於或小於目標長度時,填充字元不會起作用,而且目標長度如果小於字串本身長度時,字串也不會做截斷處理,只會原樣輸出。

padEnd函式作用同padStart,只不過它是從字串尾部做填充。來看個小例子:

'Vue'.padEnd(10, '_*')           //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello')      //'ReactHello'
'JavaScript'.padEnd(10, 'Hi')    //'JavaScript'
'JavaScript'.padEnd(8, 'Hi')     //'JavaScript'

Object.getOwnPropertyDescriptors()

顧名思義,該方法會返回目標物件中所有屬性的屬性描述符,該屬性必須是物件自己定義的,不能是從原型鏈繼承來的。先來看個它的基本用法:

let obj = {
  id: 1,
  name: 'test',
  get gender() {
    console.log('gender')
  },
  set grade(g) {
    console.log(g)
  }
}
Object.getOwnPropertyDescriptors(obj)

//輸出結果為:
{
  gender: {
    configurable: true,
    enumerable: true,
    get: f gender(),
    set: undefined
  },
  grade: {
    configurable: true,
    enumerable: true,
    get: undefined,
    set: f grade(g)
  },
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  },
  name: {
    configurable: true,
    enumerable: true,
    value: 'test',
    writable: true
  }
}

方法還提供了第二個引數,用來獲取指定屬性的屬性描述符。

let obj = {
  id: 1,
  name: 'test',
  get gender() {
    console.log('gender')
  },
  set grade(g) {
    console.log(g)
  }
}
Object.getOwnPropertyDescriptors(obj, 'id')

//輸出結果為:
{
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  }
}

由上述例子可知,該方法返回的描述符,會有兩種型別:資料描述符、存取器描述符。返回結果中包含的鍵可能的值有:configurable、enumerable、value、writable、get、set。

使用過Object.assign()的同學都知道,assign方法只能拷貝一個屬性的值,而不會拷貝它背後的複製方法和取值方法。Object.getOwnPropertyDescriptors()主要是為了解決Object.assign()無法正確拷貝get屬性和set屬性的問題。

let obj = {
  id: 1,
  name: 'test',
  get gender() {
    console.log('gender')
  }
}
Object.assign(obj)

//輸出結果為:
{
  gender: undefined
  id: 1,
  name: 'test'
}

此時,Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以實現正確拷貝。

let obj = {
  id: 1,
  name: 'test',
  get gender() {
    console.log('gender')
  }
}
let obj1 = {}
Object.defineProperties(obj1, Object.getOwnPropertyDescriptors(obj))
Object.getOwnPropertyDescriptors(obj1)

//輸出結果為:
{
  gender: {
    configurable: true,
    enumerable: true,
    get: f gender(),
    set: undefined
  },
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  },
  name: {
    configurable: true,
    enumerable: true,
    value: 'test',
    writable: true
  }
}

共享記憶體和原子(Shared memory and atomics)

ES8引入了兩部分內容:新的建構函式SharedArrayBuffer、具有輔助函式的名稱空間物件Atomics。共享記憶體允許多個執行緒併發讀寫資料,而原子操作則能夠進行併發控制,確保多個存在競爭關係的執行緒順序執行。

共享記憶體和原子也稱為共享陣列緩衝區,它是更高階的併發抽象的基本構建塊。它允許在多個工作者和主執行緒之間共享SharedArrayBuffer物件的位元組(緩衝區是共享的,用以訪問位元組,將其包裝在型別化的陣列中)。這種共享有兩個好處:

  • 可以更快地在web worker之間共享資料
  • web worker之間的協調變得更加簡單和快速

那麼,我們為什麼要引入共享記憶體和原子的概念呢?以及SharedArrayBuffer的競爭條件是什麼,Atomics又是如何解決這種競爭的?推薦下面的文章,文章講解很詳細,圖文並茂,帶你深入瞭解SharedArrayBufferAtomics

Atomics物件提供了許多靜態方法,配合SharedArrayBuffer物件一起使用,可以幫助我們去構建一個記憶體共享的多執行緒程式設計環境。Atomic操作安裝在Atomics模組上。與其他全域性物件不同,Atomics不是建構函式。您不能使用new操作符或Atomics作為函式呼叫該物件。所有的屬性和方法Atomics都是靜態的,這一點跟Math類似。下面連結貼出了Atomics提供的一些基本方法:

關於共享記憶體和原子的深入研究,也可以參考Axel Rauschmayer博士的《Exploring ES2016 and ES2017》一書中的內容。具體章節連結如下:

函式引數列表與呼叫中的尾部逗號

該特性允許我們在定義或者呼叫函式時新增尾部逗號而不報錯。

let foo = function (
  a,
  b,
  c,
) {
  console.log('a:', a)
  console.log('b:', b)
  console.log('c:', c)
}
foo(1, 3, 4, )

//輸出結果為:
a: 1
b: 3
c: 4

上面這種方式呼叫是沒有問題的。函式的這種尾逗號也是向陣列和字面量物件中尾逗號看齊,它適用於那種多行引數並且引數名很長的情況,開發過程中,如果忘記刪除尾部逗號也沒關係,ES8已經支援這種寫法。

這麼用有什麼好處呢?

首先,當我們調整結構時,不會因為最後一行程式碼的位置變動,而去新增或者刪除逗號。

其次,在版本管理上,不會出現因為一個逗號,而使本來只有一行的修改,變成兩行。例如下面:

從

(
  'abc'
)
到

(
  'abc',
  'def'
)

在我們版本管理系統裡,它會監測到你有兩處更改,但是如果我們不必去關心逗號的存在,每一行都有逗號時,新加一行,也只會監測到一行的修改。

建議的ES9功能

回想一下,每個ECMAScript功能提案都經過了幾個階段:

  • 階段4意味著功能將在下一個版本中(或之後的版本)。
  • 階段3意味著功能仍然有機會被包含在下一個版本中。

第4階段和部分ECMAScript規範草案

以下功能目前在第4階段:

  • Template Literal Revision:模板文字修訂(蒂姆·迪士尼)

候選功能(第3階段)

以下功能目前在第3階段:

  • Function.prototype.toString 修訂版(Michael Ficarra)
  • global(Jordan Harband)
  • Rest/Spread Properties:Rest/Spread屬性(SebastianMarkbåge)
  • Asynchronous Iteration:非同步迭代(Domenic Denicola)
  • import() (Domenic Denicola)
  • RegExp Lookbehind Assertions:RegExp Lookbehind斷言(Daniel Ehrenberg)
  • RegExp Unicode Property Escapes:RegExp Unicode屬性轉義(Brian Terlson,Daniel Ehrenberg,Mathias Bynens)
  • RegExp named capture groups:RegExp命名捕獲組(Daniel Ehrenberg,Brian Terlson)
  • s (dotAll) flag for regular expressions:s(dotAll)標誌為正則表示式(Mathias Bynens,Brian Terlson)
  • Promise.prototype.finally() (Jordan Harband)
  • BigInt - 任意精度整數(Daniel Ehrenberg)
  • Class fields(Daniel Ehrenberg,Jeff Morrison)
  • Optional catch binding(Michael Ficarra)

下面貼出瞭解和學習ES的官方連結,供大家查閱:

-----------------------------------------裝飾器(Decorator)-------------------------------------

類的修飾

許多面向物件的語言都有修飾器(Decorator)函式,用來修改類的行為。目前,有一個提案將這項功能,引入了 ECMAScript。


@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

上面程式碼中,@testable就是一個修飾器。它修改了MyTestableClass這個類的行為,為它加上了靜態屬性isTestabletestable函式的引數targetMyTestableClass類本身。

基本上,修飾器的行為就是下面這樣。


@decorator
class A {}

// 等同於

class A {}
A = decorator(A) || A;

也就是說,修飾器是一個對類進行處理的函式。修飾器函式的第一個引數,就是所要修飾的目標類。


function testable(target) {
  // ...
}

上面程式碼中,testable函式的引數target,就是會被修飾的類。

如果覺得一個引數不夠用,可以在修飾器外面再封裝一層函式。


function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

上面程式碼中,修飾器testable可以接受引數,這就等於可以修改修飾器的行為。

注意,修飾器對類的行為的改變,是程式碼編譯時發生的,而不是在執行時。這意味著,修飾器能在編譯階段執行程式碼。也就是說,修飾器本質就是編譯時執行的函式。

前面的例子是為類新增一個靜態屬性,如果想新增例項屬性,可以通過目標類的prototype物件操作。


function testable(target) {
  target.prototype.isTestable = true;
}

@testable
class MyTestableClass {}

let obj = new MyTestableClass();
obj.isTestable // true

上面程式碼中,修飾器函式testable是在目標類的prototype物件上新增屬性,因此就可以在例項上呼叫。

下面是另外一個例子。


// mixins.js
export function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list)
  }
}

// main.js
import { mixins } from './mixins'

const Foo = {
  foo() { console.log('foo') }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

上面程式碼通過修飾器mixins,把Foo物件的方法新增到了MyClass的例項上面。可以用Object.assign()模擬這個功能。


const Foo = {
  foo() { console.log('foo') }
};

class MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

實際開發中,React 與 Redux 庫結合使用時,常常需要寫成下面這樣。


class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

有了裝飾器,就可以改寫上面的程式碼。


@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

相對來說,後一種寫法看上去更容易理解。

方法的修飾

修飾器不僅可以修飾類,還可以修飾類的屬性。


class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

上面程式碼中,修飾器readonly用來修飾“類”的name方法。

修飾器函式readonly一共可以接受三個引數。


function readonly(target, name, descriptor){
  // descriptor物件原來的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 類似於
Object.defineProperty(Person.prototype, 'name', descriptor);

修飾器第一個引數是類的原型物件,上例是Person.prototype,修飾器的本意是要“修飾”類的例項,但是這個時候例項還沒生成,所以只能去修飾原型(這不同於類的修飾,那種情況時target引數指的是類本身);第二個引數是所要修飾的屬性名,第三個引數是該屬性的描述物件。

另外,上面程式碼說明,修飾器(readonly)會修改屬性的描述物件(descriptor),然後被修改的描述物件再用來定義屬性。

下面是另一個例子,修改屬性描述物件的enumerable屬性,使得該屬性不可遍歷。


class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}

下面的@log修飾器,可以起到輸出日誌的作用。


class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);

上面程式碼中,@log修飾器的作用就是在執行原始的操作之前,執行一次console.log,從而達到輸出日誌的目的。

修飾器有註釋的作用。


@testable
class Person {
  @readonly
  @nonenumerable
  name() { return `${this.first} ${this.last}` }
}

從上面程式碼中,我們一眼就能看出,Person類是可測試的,而name方法是隻讀和不可列舉的。

下面是使用 Decorator 寫法的元件,看上去一目瞭然。


@Component({
  tag: 'my-component',
  styleUrl: 'my-component.scss'
})
export class MyComponent {
  @Prop() first: string;
  @Prop() last: string;
  @State() isVisible: boolean = true;

  render() {
    return (
      <p>Hello, my name is {this.first} {this.last}</p>
    );
  }
}

如果同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內進入,然後由內向外執行。


function dec(id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

上面程式碼中,外層修飾器@dec(1)先進入,但是內層修飾器@dec(2)先執行。

除了註釋,修飾器還能用來型別檢查。所以,對於類來說,這項功能相當有用。從長期來看,它將是 JavaScript 程式碼靜態分析的重要工具。

轉載連結:

1.10分鐘學會ES7+ES8 https://www.cnblogs.com/zhuanzhuanfe/p/7493433.html

2.《ES6 標準入門》Decorator章節 http://es6.ruanyifeng.com/#docs/decorator