今天看到 @justjavac 寫的《ES6 中的 this & super:babel 和 typescript 都錯(cuò)了》,覺(jué)得很有意思,所以也研究了一下。
借用 @justjavac 的示例代碼,略做修改,然后在幾種語(yǔ)言中跑了一下,結(jié)果
語(yǔ)言(版本) | 輸出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 的運(yùn)行結(jié)果,畢竟它們是真正的 OOP 語(yǔ)言。另外請(qǐng)注意到,我在 TypeScript 后面加了個(gè)問(wèn)號(hào) (?)
,因?yàn)閷?shí)際上 TypeScript 雖然編譯成了對(duì)應(yīng)的 JS,但是轉(zhuǎn)譯過(guò)程中是會(huì)報(bào)錯(cuò)的:
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 說(shuō)起
對(duì)于 C#/Java 這樣的真正的 OOP 語(yǔ)言來(lái)說(shuō),super.x
和 this.x
其實(shí)是一個(gè)東西,因?yàn)樵谧宇愔袥](méi)有重新定義這個(gè)成員 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();
}
}
上面這段代碼是為了與下面這段代碼進(jìn)行比較——假如我們?cè)谧宇愔兄匦露x 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()
中實(shí)際取的是父類中的 x
,似乎有點(diǎn)不好理解——
其實(shí)也不難理解,因?yàn)樽宇愔兄匦露x了 x
,它和中的 x
就不是同一個(gè)東西了,只是正好名稱相同而已。
另一個(gè)方面來(lái)理解:子類中重新定義 x
,而不是重載(也不可能重載字段,只有方法和屬性可以重載),那么 getValue()
就不會(huì)順著虛函數(shù)鏈去找到最近的一個(gè)定義,也就不會(huì)取到子類中的賦值。
在 TypeScript 的 Playground 中運(yùn)行下面的代碼確實(shí)可以得到 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();
問(wèn)題在于,不管是在 Playground 還是 VSCode 還是 vsc(編譯器),都會(huì)得到錯(cuò)誤提示
Only public and protected methods of the base class are accessible via the 'super' keyword.
這里提到了用 super
的兩個(gè)條件,一個(gè)是 public
或 protected
修飾,二個(gè)是 methods
。第二個(gè)條件就是關(guān)鍵所在:TypeScript 中只能通過(guò) super
調(diào)用方法,所以 super.x
從語(yǔ)法上來(lái)說(shuō)就是錯(cuò)的!我不知道 Anders Hejlsberg 為什么要在語(yǔ)法錯(cuò)誤的情況仍然輸出結(jié)果——也許是為了容錯(cuò)性。但既然用 TypeScript,就是為了用它的靜態(tài)檢查,所以要充值關(guān)注編譯錯(cuò)誤提示。
現(xiàn)在來(lái)試驗(yàn)一下介于 field 和 method 之間的情況,使用 getter/setter 語(yǔ)法的屬性。
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);
}
}
很遺憾,同樣的錯(cuò)誤。就這一點(diǎn)來(lái)說(shuō),我覺(jué)得 TypeScript 還有待進(jìn)步。
ES6 的正式名稱是 ECMAScrip 2015,即 ES2015
那么,現(xiàn)在來(lái)說(shuō)說(shuō) ES6 的結(jié)果 3
、undefined
、3
。
……
可是,我除了說(shuō)不能理解之外,還能說(shuō)什么呢?
既然 super.x = 3
都可以起作用,憑什么 console.log(super.x)
就取不到值?從這一點(diǎn)上來(lái)說(shuō),Babel 的結(jié)果 (2
、undefined
、2
) 反而更符合邏輯。
ES6 的結(jié)果我不能理解,也許能從 ECMAScript 2015 Language Specification 中找到答案,不過(guò)我沒(méi)耐心去閱讀這個(gè)長(zhǎng)而枯燥的英文文檔——如果有人找到了答案,麻煩告訴我一聲,萬(wàn)分感謝!
不管怎么說(shuō),我用 TypeScript 的時(shí)間比較多,而且忠實(shí)于編譯器的錯(cuò)誤提示。因此在我實(shí)際工作中遇到類似問(wèn)題的概率非常低,不糾結(jié) ^_^
!
更多建議: