ES6 / TypeScript / Babel / C# 中的 super(base)

2018-06-09 10:30 更新

今天看到 @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ō)起

C# / Java

對(duì)于 C#/Java 這樣的真正的 OOP 語(yǔ)言來(lái)說(shuō),super.xthis.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();
    }
}

它的輸出是 23、3,為什么?

this.x2 好理解,super.x3 也好理解。而 getValue() 中實(shí)際取的是父類中的 x,似乎有點(diǎn)不好理解——

其實(shí)也不難理解,因?yàn)樽宇愔兄匦露x了 x,它和中的 x 就不是同一個(gè)東西了,只是正好名稱相同而已。

另一個(gè)方面來(lái)理解:子類中重新定義 x,而不是重載(也不可能重載字段,只有方法和屬性可以重載),那么 getValue() 就不會(huì)順著虛函數(shù)鏈去找到最近的一個(gè)定義,也就不會(huì)取到子類中的賦值。

TypeScript

在 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è)是 publicprotected 修飾,二個(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 / ES2015 / Babel

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、undefined2) 反而更符合邏輯。

小結(jié)

ES6 的結(jié)果我不能理解,也許能從 ECMAScript 2015 Language Specification 中找到答案,不過(guò)我沒(méi)耐心去閱讀這個(gè)長(zhǎng)而枯燥的英文文檔——如果有人找到了答案,麻煩告訴我一聲,萬(wàn)分感謝!

不管怎么說(shuō),我用 TypeScript 的時(shí)間比較多,而且忠實(shí)于編譯器的錯(cuò)誤提示。因此在我實(shí)際工作中遇到類似問(wèn)題的概率非常低,不糾結(jié) ^_^!

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)