HelloChunWei / blog

個人部落格,紀錄自己的知識
MIT License
7 stars 0 forks source link

Javascript decorator 你搞得我好亂呀 #1

Open HelloChunWei opened 4 years ago

HelloChunWei commented 4 years ago

前言

最近這幾天重新學習了設計模式,剛好到了裝飾者模式 (Decorator pattern) 在網路上找尋JS的簡單範例時,發現 ES7(ECMAScript2016) 有 decorator 的語法糖~經過一番波折之後發現...... ES7(ECMAScript2016) 根本沒有規範 decorator.

讓我娓娓道來

什麼是裝飾者模式

先稍微介紹一下裝飾者模式,簡單來說,在不影響原本的物件下,動態的新增其他功能,而不動到原本物件的程式碼。舉例:今天我們有個物件是 Person,他會sayHello 以及 sayGoodbye.

function Person() {};
Person.prototype.sayHello = function() {
  console.log('Hello~');
};
Person.prototype.sayGoodbye = function() {
  console.log('Goodbye~');
};
var Neil = new Person();
Neil.sayHello();
Neil.sayGoodbye();
// Hello~
// Goodbye~

但是呢,我想要可以在打招呼或再見時可以揮揮手,可以怎麼做?

第一種做法:

function Person() {};
Person.prototype.sayHello = function() {
  console.log('Hello~');
  console.log('wave hand~');
};
Person.prototype.sayGoodbye = function() {
  console.log('Goodbye~');
  console.log('wave hand~');
};
var Neil = new Person();
Neil.sayHello();
Neil.sayGoodbye();
// Hello~
// wave hand~
// Goodbye~
// wave hand~

看起來沒有什麼問題,也非常的直覺,但假設我們今天是一個套件的管理者呢?

其他開發者希望可以增加揮揮手的功能,有些人則希望可以增加自定義的打招呼方式,那我們是不是每一次都要新增功能在Person上?每新增一個功能套件就要改一次版。久而久之程式碼可能會更難維護。

實務上遇到的情況,不會像範例一樣這麼簡單,你可能會跟其他人共同維護一份套件,而共同維護的情形下,狀況及問題會複雜許多~

所以我們可以利用裝飾者模式:

function Person() {};
Person.prototype.sayHello = function() {
  console.log('Hello~');
};

Person.prototype.sayGoodbye = function() {
  console.log('Goodbye~');
};

function Decorator(param) {
  this.person = param;
};

Decorator.prototype.sayHelloAndWave = function() {
  this.person.sayHello();
  console.log('Wave hand~');
};

Decorator.prototype.sayGoodbyeAndWave = function() {
this.person.sayGoodbye();
  console.log('Wave hand~');
};

var Neil = new Decorator(new Person());
Neil.sayHelloAndWave();
//Hello~
//Wave hand

我們就可以跟用我們的套件開發者說:「你只要符合我們的規定,你可以自定義你想要的動作」

例如: 有人想要每次打招呼都要跳段舞~

function Person() {};
Person.prototype.sayHello = function() {
  console.log('Hello~');
};

Person.prototype.sayGoodbye = function() {
  console.log('Goodbye~');
};

function Decorator(param) {
  this.person = param;
};

Decorator.prototype.sayHelloAndDance = function() {
  // Do something and dance
  console.log('Dance');
  this.person.sayHello();
};
var Neil = new Decorator(new Person());
Neil.sayHelloAndDance();
// Dance
// Hello~

不過實務上不會這麼簡單,必須要完整的定義寫法,萬一有人沒按照規定的寫法寫,錯誤訊息要怎麼表示?要考慮的點非常非常的多,而且文件要寫得非常明白。

簡單的了解裝飾者模式後,我們的故事繼續下去~

上面那段 code 基本上是原生的 Javascirpt 寫法,有沒有更方便,或者更直覺的語法糖呢?

在找資料時,發現這篇文章 Exploring EcmaScript Decorators 作者似乎是在 Google 工作的工程師,而且也是在 medium/google-developers 下發表。

內文有提到 ES7(ECMAScript2016) 中有提供 decorator 的語法糖,有附有簡單的範例,話不多說,馬上試試看吧~

使用 decorator 語法糖

最一開始 decorator 我是用 codepen 去寫範例的(懶得再用babel建置)既然是在 ES7(ECMAScript2016) 的規範下,那codepen 可以用async/await ,那就可以用 decorator 吧?

but!! 實際上不行~嘗試多次之後,決定放棄codepen,改建置一個簡單的專案用babel 去編譯。

若要使用 decorator 得先安裝 babel 套件:(babel/plugin-proposal-decorators)

編譯執行後,確實是可以照著我想的樣子執行。

我有做一個簡單的範例


但在 babel 套件網站看到一段讓我覺得怪怪的文字:

legacy boolean, defaults to false. Use the legacy (stage 1) decorators syntax and behavior.

stage 1?? stage 1 代表什麼意思???

後來在 babel/plugin-proposal-decorators 最下面找到了 references

恩.....稍微看了一下,大致上是 decorator 的用法,不過在該篇文章最上面表示: This has moved to https://github.com/tc39/proposal-decorators

點進去後發現來到了TC39的github頁面。(我是誰?我在哪?我為什麼會在這裡?)

TC39 是什麼?

沒關係,我們坐下來,洗個澡,一個一個的把問題解決。首先,要先知道tc39是什麼?

在找尋資料的時候發現了這文章,裡面有簡易的介紹TC 39這個組織。

作者是 Axel Rauschmayer twitter 上有5.7萬追隨者,也出過幾本書,算是在 javascript 界滿有權威的人。

tc39 原名為 Technical Committee 39 基本上是為了改進 javascirpt 的組織,裡面的成員大多都是公司(瀏覽器開發商等等.....),ECMAscript 就是他們設計的。

TC39 的流程

該篇文章有提到: 由於ES6發布後花了將近6年才將其標準化 (2019-2015) ,為了避免有類似的情形發生,他們開發了新的流程,保持一年出新的一版,每年新增的內容也不會太多。

所以我們都會說 ECMAScript2015 = ES6 但實際上官方的說法都是以年份為主EX: ECMAScript2015ECMAScript2016 等等...... 在 Exploring ES6 中,作者 Axel Rauschmayer 也提出由於新的流程出現,該是以 ECMAScript20XX 作為新的命名方式。不過在這,我會兩種命名都寫,方便閱讀。

流程一共分成五個 stage,從 stage0 到 stage4,到最後的 stage4 之後,就表示新的內容可以加入到 ECMAScript 中。

如果想知道更詳細的流程可以到 The TC39 Process 了解,這裡就不多做說明。

所以我說那個 decorator 呢?

從 TC39 的流程中,我們可以看到關鍵字 stage 2,這跟 proposal-decorators 中是在一樣的階段,所以代表 decorator 的語法糖並沒有在 ES7(ECMAScript2016) 規範中囉?

我到了該年規範中發現該年跟一般開發者比較相關的 feature 是 Array.prototype.includes. (等等!? 所以 async/await 並沒有在 ECMAScript2016 中!?)

從文件中可以得知 decorator 並沒有在 ES7 (ECMAScript2016) 的規範內。

重新整理一次

把疑問都解開之後,我又再一次的回去看那篇文章 Exploring EcmaScript Decorators 再仔細的看一次,發覺該篇作者確實是有說 decorator 的語法糖是在提案階段,例如文章前段的:

Update 07/29/17: Decorators are advancing at TC39. The latest work on them can be found in the proposals repo. Several new examples are also now up.

以及後半段的:

Decorators (at the time of writing) are still but a proposal. They haven’t yet been approved. That said, thankfully Babel supports transpilation of the syntax in an experimental mode, so most of the samples from this post can be tried out with it directly.

但如果在實驗階段的話,為什麼他會說 ES2016 Decorators 呢?這讓我很疑惑。

Axel Rauschmayer 也提到: Don’t call them ECMAScript 20xx features 除非是到了stage4,不然不應該把那些還正在提案中的 feature 說是在到某年的規範中,畢竟也是有提案到一半被放棄的例子。在這點上,我是滿認同 Axel Rauschmayer 的觀點。

結論

decorator 並沒有在 ES7 (ECMAScript2016) 的規範內。但可以透過 babel 讓我們先行體驗這語法糖

所以這也可以解釋為何沒辦法很順利的在 codepen 使用。 (或許可以,如果真的可以的話,請告知我,謝謝~)

這一次的經驗算是滿特別的,從一個設計模式延伸到TC39,以及ECMAScript,雖然實務上可能不會用到這些知識,不過把心中的疑惑解開,那感覺也是滿棒的~

而且非常佩服那些貢獻者,要不斷的開會、測試,確認沒問題之後,才會正式標準化,也非常佩服 babel,webpack,nodeJS 的團隊。不斷的讓開發者們可以使用新的規範。

哦對了~~ 剛剛查了一下 TC39 proposals,原來async function 並不是 ES7 (ECMAScript2016) 的標準呀,是 ES8 (ECMAScript2017) 的標準。

後記

後來我又在網路上搜尋 「javascript decorator」 發現有些文章會把他歸類 ES7(ECMAScript2016),有些文章則清楚表示他則是在提案階段。經過這一次經驗後,找資料時最好的方法就是去找原始文件。

在這個例子就是要去看 ECMAScript2016 或者 tc39/proposals 去確認 decorator 是否已經在規範中,還是在提案階段

參考資料:

  1. Exploring EcmaScript Decorators
  2. Decorator pattern
  3. Exploring EcmaScript Decorators
  4. babel/plugin-proposal-decorators
  5. proposal-decorators
  6. tc39-process by Axel Rauschmayer
  7. tc39/proposals
  8. ECMAScript2015
  9. ECMAScript2016
  10. Exploring ES6
samson-sham commented 1 year ago

寫得很好! 對TC39和stage的解釋很詳盡. 最近Typescript 5支援了decorator的寫法想說TC39有通過過decorator嗎就闖進來了

HelloChunWei commented 1 year ago

@samson-sham 謝謝 XD 原本只是想說留個記錄讓我自己看看,沒想到真的有其他讀者XD

不過目前我自己還在思考 decorator 的使用情境。現在沒有太多的想法

YamiOdymel commented 10 months ago

Google 出來第一篇就是這個,笑死

某方面看起來像 Wrapper,我以為 Decorator 已經實裝幾年了,沒想到還沒