今天看到 @justjavac 寫的《ES6 中的 this & super:babel 和 typescript 都錯了》,覺得很有意思,所以也研究了一下。
借用 @justjavac 的示例程式碼,略做修改,然後在幾種語言中跑了一下,結果
語言(版本) | 輸出1 | 輸出2 | 輸出3 |
---|---|---|---|
ES6 | 3 | undefined | 3 |
Babel | 2 | undefined | 2 |
TypeScript (?) | 2 | 3 | 2 |
C# | 3 | 3 | 3 |
Java | 3 | 3 | 3 |
是的,我加入了 C# 和 Java 的執行結果,畢竟它們是真正的 OOP 語言。另外請注意到,我在 TypeScript 後面加了個問號 (?)
,因為實際上 TypeScript 雖然編譯成了對應的 JS,但是轉譯過程中是會報錯的:
index.ts (20,15): Only public and protected methods of the base class are accessible via the `super` keyword. (2340)
index.ts (22,27): Only public and protected methods of the base class are accessible via the `super` keyword. (2340)
下面,我從 C#/Java 說起
C# / Java
對於 C#/Java 這樣的真正的 OOP 語言來說,super.x
和 this.x
其實是一個東西,因為在子類中沒有重新定義這個成員 x
。以 C# 程式碼為例
using System;
public class Program
{
public static void Main()
{
var t = new ColorPoint();
t.test();
}
}
class Point {
public int x;
protected void getValue() {
Console.WriteLine(this.x);
}
}
class ColorPoint : Point {
public ColorPoint() {
this.x = 2;
base.x = 3;
Console.WriteLine(this.x);
Console.WriteLine(base.x);
}
public void test() {
this.getValue();
}
}
上面這段程式碼是為了與下面這段程式碼進行比較——假如我們在子類中重新定義 x
呢?
class ColorPoint : Point {
public new int x;
public ColorPoint() {
this.x = 2;
base.x = 3;
Console.WriteLine(this.x);
Console.WriteLine(base.x);
}
public void test() {
this.getValue();
}
}
它的輸出是 2
、3
、3
,為什麼?
this.x
是 2
好理解,super.x
是 3
也好理解。而 getValue()
中實際取的是父類中的 x
,似乎有點不好理解——
其實也不難理解,因為子類中重新定義了 x
,它和父類中的 x
就不是同一個東西了,只是正好名稱相同而已。
另一個方面來理解:子類中重新定義 x
,而不是過載(也不可能過載欄位,只有方法和屬性可以過載),那麼 getValue()
就不會順著虛擬函式鏈去找到最近的一個定義,也就不會取到子類中的賦值。
TypeScript
在 TypeScript 的 Playground 中執行下面的程式碼確實可以得到 2
、3
、2
:
class Point {
public x: number;
protected getValue() {
console.log(this.x);
}
}
class ColorPoint extends Point {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(this.x);
console.log(super.x);
}
test() {
this.getValue();
}
}
const t = new ColorPoint();
t.test();
問題在於,不管是在 Playground 還是 VSCode 還是 vsc(編譯器),都會得到錯誤提示
Only public and protected methods of the base class are accessible via the `super` keyword.
這裡提到了用 super
的兩個條件,一個是 public
或 protected
修飾,二個是 methods
。第二個條件就是關鍵所在:TypeScript 中只能通過 super
呼叫方法,所以 super.x
從語法上來說就是錯的!我不知道 Anders Hejlsberg 為什麼要在語法錯誤的情況仍然輸出結果——也許是為了容錯性。但既然用 TypeScript,就是為了用它的靜態檢查,所以要充分關注編譯錯誤提示。
現在來試驗一下介於 field 和 method 之間的情況,使用 getter/setter 語法的屬性。
class Point {
private _x: number;
public get x(): number {
return this._x;
}
public set x(value: number) {
this._x = value;
}
protected getValue() {
console.log(this.x);
}
}
很遺憾,同樣的錯誤。就這一點來說,我覺得 TypeScript 還有待進步。
ES6 / ES2015 / Babel
ES6 的正式名稱是 ECMAScrip 2015,即 ES2015
那麼,現在來說說 ES6 的結果 3
、undefined
、3
。
……
可是,我除了說不能理解之外,還能說什麼呢?
既然 super.x = 3
都可以起作用,憑什麼 console.log(super.x)
就取不到值?從這一點上來說,Babel 的結果 (2
、undefined
、2
) 反而更符合邏輯。
小結
ES6 的結果我不能理解,也許能從 ECMAScript 2015 Language Specification 中找到答案,不過我沒耐心去閱讀這個長而枯燥的英文文件——如果有人找到了答案,麻煩告訴我一聲,萬分感謝!
不管怎麼說,我用 TypeScript 的時間比較多,而且忠實於編譯器的錯誤提示。因此在我實際工作中遇到類似問題的概率非常低,不糾結 ^_^
!