TypeScript:從零開始的前端面向?qū)ο驲PG

耶穌說(shuō):“凱撒的物當(dāng)歸給凱撒,神的物當(dāng)歸給神?!薄恶R太福音22:21》

抱歉我其實(shí)不是個(gè)基督徒。我用這句話的原因是因?yàn)檫@句話很符合面向?qū)ο笏枷耄鹤屢粋€(gè)角色做好它該做的事。如何理解這句話呢?就好比你去拿著一堆的原材料去找一個(gè)鐵匠,他就能給你打造一把你想要的武器;如果你拿著同樣這堆原材料去找一個(gè)廚子,他會(huì)拿炒勺弄死你。

道理我們都懂?但是這個(gè)跟寫代碼有什么關(guān)系嗎?當(dāng)然有關(guān)系!勇者喲,在踏上征途之前,我們先在新手村做一些準(zhǔn)備工作吧!

創(chuàng)世界:準(zhǔn)備工作

上路之前,你要準(zhǔn)備好一些東西,比如Node.js和npm。安裝Node.js的傳送門在這里。然后還需要一個(gè)世界轉(zhuǎn)換器TypeScript:

npm install -g typescript

TypeScript現(xiàn)在是這個(gè)世界的規(guī)則。它跟JavaScript世界很像,嗯,甚至沒什么差別,比如:

ts-the-rpg.ts(v1

function hello(hero) {
    console.log("Hello , " + hero);
}
var hero = "勇者";

hello(hero);

我們需要將ts-the-rpg.ts變成JavaScript世界的才能玩。

tsc ts-the-rpg.ts

這個(gè)時(shí)候我們發(fā)現(xiàn)生成了ts-the-rpg.js。我們打開看看,發(fā)現(xiàn),怎么這兩個(gè)世界一模一樣?

沒錯(cuò),這兩個(gè)世界幾乎一樣。但是接下來(lái)不一樣的來(lái)了:

ts-the-rpg.ts(v2)

function hello(hero: string) {
    console.log("Hello , " + hero);
}
var hero1 = "勇者";
var hero2 = 2;

hello(hero1);
hello(hero2);

你在轉(zhuǎn)換世界的時(shí)候發(fā)現(xiàn),怎么出了一個(gè)問(wèn)題?

ts-the-rpg.ts(8,7): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

看起來(lái)有一個(gè)怪物混入了勇者的隊(duì)伍之中啊。勇者都有自己的名字,你卻是一個(gè)數(shù)字?你想蒙混過(guò)關(guān)?世界轉(zhuǎn)換器就會(huì)告訴你,你很危險(xiǎn)了勇者。

但是即使這樣,世界轉(zhuǎn)換器很公正,它還是把TypeScript世界的一切帶到了JavaScript世界。因?yàn)镴avaScript世界中,勇者、怪物,傻傻分不清楚。轉(zhuǎn)換后的世界:

ts-the-rpg.js

function hello(hero) {
    console.log("Hello , " + hero);
}
var hero1 = "勇者";
var hero2 = 2;
hello(hero1);
hello(hero2);

你是一個(gè)創(chuàng)世者,也是一個(gè)游戲玩家,世界轉(zhuǎn)換器告訴了你這個(gè)世界有問(wèn)題,但是你的世界你來(lái)決定。我建議,為了這個(gè)世界不至于在最后分崩離析,還是好好處理完問(wèn)題再上路吧。

數(shù)據(jù)類型,是構(gòu)建這個(gè)世界的基礎(chǔ)。這個(gè)世界本身由這么幾個(gè)基本數(shù)據(jù)類型組成:string、number、array、enum、any……等等。我們?cè)趺磁袛嗨遣皇沁@種類型的怎么做?只要把類型帶在你的聲明之中。比如function hello(hero: string)就表示,你必須是一個(gè)string類型的數(shù)據(jù),才能通過(guò)這里。

新手村:歡迎你,勇者

“你好,勇者!”突然你看到了一陣光,看板娘站在光中,笑靨如畫。

你想起來(lái)了,你是一個(gè)勇者。等等,什么是勇者?你作為一個(gè)創(chuàng)世者,深深的陷入了思考,最后得出一個(gè)結(jié)論:

ts-the-rpg.ts(v3)

var hero = {
    name: "勇者",
    hp: 10
}
function hello(hero) {
    console.log("Hello , " + hero.name);
}
hello(hero);

不對(duì),這樣做不是依然什么樣的牛鬼蛇神都能以勇者的身份進(jìn)入這個(gè)世界嗎?嗯,作為一個(gè)先知,我告訴你怎么做:

ts-the-rpg.ts(v4)

// 定義什么是勇者
class Hero {
    name: string;   // 每個(gè)勇者都有一個(gè)名字
    hp: number;     // 每個(gè)勇者有自己的HP值
    // 召喚一個(gè)勇者的規(guī)則
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 召喚一個(gè)勇者
var hero = new Hero("勇者", 10);

// 只能由勇者通過(guò)的路
function hello(hero: Hero) {
    console.log("Hello , " + hero.name);
}

hello(hero);
hello("我也是一個(gè)勇者?。?);

我們先來(lái)試試看轉(zhuǎn)換這個(gè)世界,之后再來(lái)解釋一下為什么這么做。開始轉(zhuǎn)換:

ts-the-rpg.ts(21,7): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Hero'.

最后一個(gè)假裝是勇者的字符串想要蒙混過(guò)關(guān),被轉(zhuǎn)換器攔住了。等等,轉(zhuǎn)換器怎么知道勇者長(zhǎng)什么樣?

沒錯(cuò),我們這時(shí)候祭出了勇者召喚的特殊形式:class。我們通過(guò)定義一個(gè)叫Hero的數(shù)據(jù)類型來(lái)告訴世界,這個(gè)世界開始有勇者了。那么以后,我們就可以在入口處判斷你是不是一個(gè)Hero。

class由這么幾個(gè)部分組成:它是什么(定義的數(shù)據(jù)類型名)、它由什么構(gòu)成(類的成員數(shù)據(jù))、它能做什么(定義數(shù)據(jù)類型的行為)、它需要哪些素材才能被召喚出來(lái)(構(gòu)造函數(shù))。

ts-the-rpg.ts(v4)上看,我們定義了它是勇者(class Hero),它有名字(name: string;)和血量(hp: number;),召喚他的規(guī)則就是必須要給他起個(gè)名字并提供血量(constructor(name: string, hp: number){ this.name = name; this.hp = hp; }),然后通過(guò)特殊儀式召喚它(var hero = new Hero("勇者", 10);)。

那你會(huì)問(wèn),如果有一個(gè)史萊姆,偽裝的特別像一個(gè)勇者,就像下面這樣,世界轉(zhuǎn)換器會(huì)怎么做?

ts-the-rpg.ts(v5)

// 定義什么是勇者
class Hero {
    name: string;   // 每個(gè)勇者都有一個(gè)名字
    hp: number;     // 每個(gè)勇者有自己的HP值
    // 召喚一個(gè)勇者的規(guī)則
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 召喚一個(gè)勇者
var hero = new Hero("勇者", 10);

// 只能由勇者通過(guò)的路
function hello(hero: Hero) {
    console.log("Hello , " + hero.name);
}

hello(hero);
// 偽裝成勇者的史萊姆
hello({ name: "我不是史萊姆", hp: 1 });

我們?cè)囍D(zhuǎn)換到普通世界后,怎么世界轉(zhuǎn)換器什么都沒做?于是你陷入了深深的恐懼。沒錯(cuò),這樣召喚一個(gè)勇者肯定一瞬間就被危險(xiǎn)的史萊姆識(shí)破并且偽裝,這個(gè)時(shí)候我們需要把勇者不為人知的一面隱藏起來(lái),比如勇者有hp這件事。

ts-the-rpg.ts(v6)

// 定義什么是勇者
class Hero {
    name: string;           // 每個(gè)勇者都有一個(gè)名字
    private hp: number;     // 每個(gè)勇者有自己的HP值,但是受保護(hù)
    // 召喚一個(gè)勇者的規(guī)則
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 召喚一個(gè)勇者
var hero = new Hero("勇者", 10);

// 只能由勇者通過(guò)的路
function hello(hero: Hero) {
    console.log("Hello , " + hero.name);
}

hello(hero);
hello({ name: "我不是史萊姆", hp: 1 });

這時(shí)候我們轉(zhuǎn)換這個(gè)世界,你看史萊姆被攔在了外面:

ts-the-rpg.ts(21,7): error TS2345: Argument of type '{ name: string; hp: number; }' is not assignable to parameter of type 'Hero'.

Property 'hp' is private in type 'Hero' but not in type '{ name: string; hp: number; }'.

沒錯(cuò),你一個(gè)堂堂史萊姆,把hp值這種對(duì)于勇者如此重要的屬性暴露在外面,這種作風(fēng)肯定不是勇者所為,你出去。

這時(shí)候史萊姆又來(lái)搞事情,它想,如果這樣,我也隱藏我的hp,除了定義不一樣,其他的都一摸一樣,那樣我一定能進(jìn)去。

ts-the-rpg.ts(v7)

// 定義什么是勇者
class Hero {
    name: string;           // 每個(gè)勇者都有一個(gè)名字
    private hp: number;     // 每個(gè)勇者有自己的HP值,但是受保護(hù)
    // 召喚一個(gè)勇者的規(guī)則
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 定義什么是史萊姆
class Slime {
    name: string;           // 每個(gè)史萊姆都有一個(gè)名字
    private hp: number;     // 每個(gè)史萊姆有自己的HP值,但是受保護(hù)
    // 召喚一個(gè)史萊姆的規(guī)則
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 召喚一個(gè)勇者
var hero = new Hero("勇者", 10);
var slime = new Slime("勇者", 10);

// 只能由勇者通過(guò)的路
function hello(hero: Hero) {
    console.log("Hello , " + hero.name);
}

hello(hero);
hello(slime);

這時(shí)候世界轉(zhuǎn)換器非常聰明:

ts-the-rpg.ts(33,7): error TS2345: Argument of type 'Slime' is not assignable to parameter of type 'Hero'.

Types have separate declarations of a private property 'hp'.

你作為一只史萊姆,身上流著史萊姆的血,你的血的味道,我一聞就能聞出來(lái)不一樣。史萊姆直接被推了出去。

那你肯定這時(shí)候肯定覺得很奇怪ts-the-rpg.ts(v5)ts-the-rpg.ts(v7)同樣是將史萊姆偽裝成了勇者,為什么v5成功了,v7卻失敗了?

世界轉(zhuǎn)換器在這里是這么做處理的:

  1. v5部分因?yàn)樗械牟糠侄际枪_的,那么他只會(huì)判斷是否存在,這種原理別處稱作“鴨子模型”,就是說(shuō)“呱呱叫又會(huì)游泳的鳥那就肯定是鴨子”,在這里就是“有名字有hp的肯定是勇者”,所以就放行了,這是一種弱的類型檢查機(jī)制,可以抵擋住大部分的偽裝。

  2. 在v7中,有部分是隱藏的,那么不只會(huì)判斷隱藏的部分是不是存在的,還會(huì)判斷它是否來(lái)自不同的定義。

好了,一切都安全了。你到這里應(yīng)該明白了TypeScript世界的一部分,類型檢查。這個(gè)特性能夠在某種程度上保護(hù)你程序的安全,不至于讓你在每個(gè)通道內(nèi)設(shè)置關(guān)卡,判斷進(jìn)來(lái)的東西是勇者還是史萊姆,或者是偽裝成勇者的史萊姆。在JavaScript的世界里,你需要處處小心,勇者即使進(jìn)了城也要被處處浪費(fèi)時(shí)間去盤問(wèn),而在TypeScript中,勇者只需要在城門口被盤問(wèn)一邊,確定你是勇者后,你在城里能得到所有你能得到的東西,而不用再一遍一遍的被盤問(wèn):“你是不是勇者?”

轉(zhuǎn)職:你依然是個(gè)勇者

你站在新手村中心,不知所措的時(shí)候,邊上有三個(gè)導(dǎo)師,分別在招攬著自己的學(xué)徒,分別是戰(zhàn)士、魔法師和弓箭手:

ts-the-rpg.ts(v8)

class Hero {
    name: string;
    private hp: number;
    // 勇者的召喚方式
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

class Warrior extends Hero {
    weapon: string;
    // 戰(zhàn)士的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    swing() {
        console.log("swing");
    }
}

class Magician extends Hero {
    weapon: string;
    // 魔法師的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    fireball() {
        console.log("fireball");
    }
}

class Archer extends Hero {
    weapon: string;
    // 弓箭手的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    shoot() {
        console.log("shoot");
    }
}

沒錯(cuò),三種職業(yè)有著自己的攻擊方式。勇者你要學(xué)什么呢?

ts-the-rpg.ts(v9)

class Hero {
    name: string;
    private hp: number;
    // 勇者的召喚方式
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 通過(guò)extends繼承了勇者之力
class Warrior extends Hero {
    weapon: string;
    // 戰(zhàn)士的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    swing() {
        console.log("swing");
    }
}

// 通過(guò)extends繼承了勇者之力
class Magician extends Hero {
    weapon: string;
    // 魔法師的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    fireball() {
        console.log("fireball");
    }
}

// 通過(guò)extends繼承了勇者之力
class Archer extends Hero {
    weapon: string;
    // 弓箭手的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    shoot() {
        console.log("shoot");
    }
}

function forest(hero: Hero) {
    console.log("Enter Forest !!");
}

var hero1 = new Warrior("warrior", 10, "sword");
var hero2 = new Magician("magician", 10, "wand");
var hero3 = new Archer("archer", 10, "bow");

forest(hero1);
forest(hero2);
forest(hero3);

世界轉(zhuǎn)換器轉(zhuǎn)換后發(fā)現(xiàn),竟然戰(zhàn)士、法師和弓箭手都能進(jìn)入森林!

你要知道,即使你選擇了職業(yè),但是你體內(nèi)的勇者之名和勇者之血都通過(guò)extends Hero方式繼承了下來(lái)。你的召喚方式變了,但是你的召喚規(guī)則里還通過(guò)super(name, hp);這個(gè)方式保留著你的內(nèi)心,這個(gè)就像是當(dāng)初召喚你的方式,new Hero(name , hp)。所以即使這時(shí)候你們用著不同的武器,有著不同的攻擊方式,卻依然內(nèi)心都還是個(gè)勇者。

技能訓(xùn)練:技能雖好,可不要偷師哦!

這時(shí)候你猶豫了,你想去三個(gè)練功房都看看,再來(lái)考慮轉(zhuǎn)職的事情:

ts-the-rpg.ts(v10)

class Hero {
    name: string;
    private hp: number;
    // 勇者的召喚方式
    constructor(name: string, hp: number) {
        this.name = name;
        this.hp = hp;
    }
}

// 通過(guò)extends繼承了勇者之力
class Warrior extends Hero {
    weapon: string;
    // 戰(zhàn)士的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    swing() {
        console.log("swing");
    }
}

// 通過(guò)extends繼承了勇者之力
class Magician extends Hero {
    weapon: string;
    // 魔法師的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    fireball() {
        console.log("fireball");
    }
}

// 通過(guò)extends繼承了勇者之力
class Archer extends Hero {
    weapon: string;
    // 弓箭手的召喚方式
    constructor(name: string, hp: number , weapon: string) {
        // 你的名字和你的血液是勇者的名字和勇者的血液,這是你的內(nèi)心
        super(name, hp);
        this.weapon = weapon;
    }
    shoot() {
        console.log("shoot");
    }
}

function trainWarrior(hero: Warrior) {}
function trainMagician(hero: Magician) {}
function trainArcher(hero: Archer) {}

var hero = new Hero("普通勇者", 10);

trainWarrior(hero);
trainMagician(hero);
trainArcher(hero);

毫不意外,你被三個(gè)練功房都踢了出來(lái)。

ts-the-rpg.ts(60,14): error TS2345: Argument of type 'Hero' is not assignable to parameter of type 'Warrior'.

Property 'weapon' is missing in type 'Hero'.

ts-the-rpg.ts(61,15): error TS2345: Argument of type 'Hero' is not assignable to parameter of type 'Magician'.

Property 'weapon' is missing in type 'Hero'.

ts-the-rpg.ts(62,13): error TS2345: Argument of type 'Hero' is not assignable to parameter of type 'Archer'.

Property 'weapon' is missing in type 'Hero'.

三個(gè)房間的導(dǎo)師都說(shuō),你沒有武器,學(xué)不了技能。村長(zhǎng)這個(gè)時(shí)候走過(guò)來(lái)告訴你真相:即使你有了武器,你也學(xué)不了。對(duì),職人是繼承了勇者的內(nèi)心,職人永遠(yuǎn)都是勇者,而勇者沒有轉(zhuǎn)職,沒有職人才有的能力,你永遠(yuǎn)只是個(gè)勇者,不是一個(gè)職人。這就是繼承的真相。

職人是勇者,勇者不是職人。

職人是勇者,勇者不是職人。

職人是勇者,勇者不是職人。

你默默念了三遍。銘記在心。

各式各樣的武器:選一件吧少年

實(shí)際上專職沒有那么簡(jiǎn)單,你的武器不僅僅是個(gè)名字而已,這時(shí)候你的老師讓你去挑一把武器帶過(guò)來(lái)。你去了武器鋪。武器鋪的鐵匠大叔一看到你是一個(gè)勇者,非常熱心的跟你介紹了不同的武器。

ts-the-rpg.ts(v11)

class Weapon {
    name: string;
    private: atk;
    constructor(name: string, atk: number) {
        this.name = name;
        this.atk = atk;
    }
}

class Sword extends Weapon {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    swing() {
        console.log("swing");
    }
}

class Wand extends Weapon {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    fireball() {
        console.log("fireball");
    }
}

class Bow extends Weapon {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    shoot() {
        console.log("shoot");
    }
}

你說(shuō)你要轉(zhuǎn)職成戰(zhàn)士,你只看劍。他很熱情,問(wèn)問(wèn)你是不是要附魔。有火焰效果和寒冰效果,然后可以給你打造一把獨(dú)一無(wú)二的劍:

ts-the-rpg.ts(v12)

class Weapon {
    name: string;
    private: atk;
    constructor(name: string, atk: number) {
        this.name = name;
        this.atk = atk;
    }
}

class Sword extends Weapon {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    swing() {
        console.log("swing");
    }
}

interface Fire {
    fire();
}

interface Ice {
    ice();
}

你想了想說(shuō),都要。鐵匠一愣,笑了笑,你小子為難老夫!好,難不倒老夫,于是你的劍做好了:

ts-the-rpg.ts(v12)

class Weapon {
    name: string;
    private: atk;
    constructor(name: string, atk: number) {
        this.name = name;
        this.atk = atk;
    }
}

class Sword extends Weapon {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    swing() {
        console.log("swing");
    }
}

interface Fire {
    fire();
}

interface Ice {
    ice();
}

class SwordOfIceAndFire extends Sword implements Ice, Fire {
    constructor(name: string, atk: number) {
        super(name, atk);
    }
    swing() {
        console.log("swing");
        this.ice();
        this.fire();
    }
    ice() {
        console.log("ice");
    }
    fire() {
        console.log("fire");
    }
}

沒錯(cuò),interface在面向?qū)ο笾芯秃孟袷歉侥傩?,只要你愿意,可以通過(guò)implements無(wú)限的往一把劍上疊加能力。但是,即使你無(wú)限疊加了能力,它還是一把劍,而不能成為魔杖或者弓箭。

冒險(xiǎn)才剛剛開始,而我們的教程到了尾聲。我們?cè)賮?lái)回顧一下有哪些概念:

  1. 類和類型檢查:一個(gè)史萊姆偽裝的再好,還是史萊姆。
  2. 繼承是什么:勇者轉(zhuǎn)職之后還是勇者,不管他變成了劍士、魔法師還是弓箭手。
  3. 子類和父類的繼承關(guān)系:一個(gè)職人是勇者,但是一個(gè)勇者不是職人。
  4. 接口是什么:附魔屬性。
  5. 通過(guò)接口擴(kuò)展類:只要你愿意,可以無(wú)限的往一把劍上疊加能力,讓它成為一把新的劍。但是,即使你無(wú)限疊加了能力,它還是一把劍,而不能成為魔杖或者弓箭。

希望通過(guò)這個(gè)教程幫助你理解TypeScript中面向?qū)ο蟮幕A(chǔ)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容