ES6 Class Methods 定義方式的差異
引言
在 JavaScript 中有兩條不成文的說法:
一切皆對象
函數是一等公民
因而函數不僅是一等公民,也是具有屬性的特殊對象。這一點,也可以從原型鏈上得到佐證:
函數是繼承自 的,因而函數也具備 、 等方法。因為函數是對象,所以在 ES6 之前,JavaScript 中的 OOP 編程則純粹是基於函數的,直到 ES6 提供了 、 以及 等關鍵字,不僅精簡了語法,也使得 OOP 的編程形式逐漸趨近於 Java/C++ 等語言。
class 的背後
ES6 雖然提供了 等關鍵字,但只是語法糖,JavaScript 的 OOP 編程仍然是基於函數的,繼承則是基於原型的。
看一個示例:
上述代碼經過 babel 轉換之後:
可以看到,轉換後的 class A 就是一個函數,所以理論上就可以把 A 當作函數調用,但 的作用就是禁止將類作為函數調用:
然後看下 都做了什麼:
通過上述代碼可知, 的功能主要是通過 定義了類的普通屬性和靜態屬性。需要注意的是普通屬性是定義在了類的原型對象上,靜態屬性是定義在了類本身上。
所以,類 A 的定義就等同於如下代碼:
兩種定義 Methods 的方式
ES6 中有兩種常見的定義 Methods 的方式:
咋一看,二者沒什麼區別。方式一是常規方式,方式二是通過箭頭函數來定義方法,如果你寫過 React 應用,應該接觸過這種方式。
區別1:this 的綁定
在箭頭函數出現之前,每個新定義的函數都有它自己的 值,但箭頭函數不會創建自己的 ,它從會從自己的作用域鏈的上一層繼承 。舉個粟子:
當點擊 元素時,會觸發 ,該方法會輸出當前的 ,而(嚴格模式下)此時輸出的 值是 ,顯然這不是我們要的結果。怎麼修改呢?這裡至少有三種修改方式,其中之一就是通過箭頭函數來定義方式。
區別2:繼承
先看方式一的繼承:
對於上述結果的輸出應該沒有什麼疑問,這是符合我們預期的。然後看下另一段代碼:
上述的輸出會是什麼呢?按照常規思路,應該是先輸出 ,再輸出 ,但其實不是的。
上文有提到,類的繼承依然是基於原型的。上文也分析過 babel 轉換過的代碼,常規的寫法中,類的非靜態屬性都是定義在類的原型對象上,而不是類的實例上的。但箭頭函數不一樣,通過箭頭函數定義的方法時綁定在 上,而 是指向當前創建的類實例對象,而不是類的原型對象。可以查看類 B 轉換後的代碼:
可以看到, 方法是定義在 上的,而不是定義在 上。
類 繼承 ,不僅會繼承類 原型上的屬性和方法,也會繼承其實例上的屬性和方法。那麼,此時類 等效的偽代碼如下:
綜上,當 執行時,只會輸出 ,而不會輸出 。
因為當訪問一個對象實例的屬性時,會先在實例上進行查找,如果沒有,則順著原型鏈往上查找,直到原型鏈的頂端。若在實例上查找到對應屬性,則會返回,停止查找。即使原型上定義了同一個屬性,該屬性也不會被訪問到,這種情況稱為"屬性遮蔽 (property shadowing)"。


※nodejs 的模板引擎選擇
※避免陷入 async/await 地獄
TAG:Pomy隨記 |