ES6:類(lèi)

引用:http://www.tuicool.com/articles/BFf6jiB

從本質(zhì)上說(shuō),ES6的classes主要是給創(chuàng)建老式構(gòu)造函數(shù)提供了一種更加方便的語(yǔ)法,并不是什么新魔法 —— Axel Rauschmayer,Exploring ES6作者
從功能上來(lái)講,class聲明就是一個(gè)語(yǔ)法糖,它只是比我們之前一直使用的基于原型的行為委托功能更強(qiáng)大一點(diǎn)。本文將從新語(yǔ)法與原型的關(guān)系入手,仔細(xì)研究ES2015的class關(guān)鍵字。文中將提及以下內(nèi)容:

  • 定義與實(shí)例化類(lèi);
  • 使用extends創(chuàng)建子類(lèi);
  • 子類(lèi)中super語(yǔ)句的調(diào)用;
  • 以及重要的標(biāo)記方法(symbol method)的例子。

在此過(guò)程中,我們將特別注意 class 聲明語(yǔ)法從本質(zhì)上是如何映射到基于原型代碼的。

退一步說(shuō):Classes不是什么

JavaScript的『類(lèi)』與Java、Python或者其他你可能用過(guò)的面向?qū)ο笳Z(yǔ)言中的類(lèi)不同。其實(shí)后者可能稱(chēng)作面向『類(lèi)』的語(yǔ)言更為準(zhǔn)確一些。
在傳統(tǒng)的面向類(lèi)的語(yǔ)言中,我們創(chuàng)建的類(lèi)是對(duì)象的模板。需要一個(gè)新對(duì)象時(shí),我們實(shí)例化這個(gè)類(lèi),這一步操作告訴語(yǔ)言引擎將這個(gè)類(lèi)的方法和屬性復(fù)制到一個(gè)新實(shí)體上,這個(gè)實(shí)體稱(chēng)作實(shí)例。實(shí)例是我們自己的對(duì)象,且在實(shí)例化之后與父類(lèi)毫無(wú)內(nèi)在聯(lián)系。
而JavaScript沒(méi)有這樣的復(fù)制機(jī)制。在JavaScript中『實(shí)例化』一個(gè)類(lèi)創(chuàng)建了一個(gè)新對(duì)象,但這個(gè)新對(duì)象卻不獨(dú)立于它的父類(lèi)。
正相反,它創(chuàng)建了一個(gè)與原型相連接的對(duì)象。即使是在實(shí)例化之后,對(duì)于原型的修改也會(huì)傳遞到實(shí)例化的新對(duì)象去。
原型本身就是一個(gè)無(wú)比強(qiáng)大的設(shè)計(jì)模式。有許多使用了原型的技術(shù)模仿了傳統(tǒng)類(lèi)的機(jī)制,class便為這些技術(shù)提供了簡(jiǎn)潔的語(yǔ)法。
總而言之:

  • JavaScript不存在Java和其他面向?qū)ο笳Z(yǔ)言中的類(lèi)概念;
  • JavaScript 的class很大程度上只是原型繼承的語(yǔ)法糖,與傳統(tǒng)的類(lèi)繼承有很大的不同。

類(lèi)基礎(chǔ):聲明與表達(dá)式

我們使用class 關(guān)鍵字創(chuàng)建類(lèi),關(guān)鍵字之后是變量標(biāo)識(shí)符,最后是一個(gè)稱(chēng)作類(lèi)主體的代碼塊。這種寫(xiě)法稱(chēng)作類(lèi)的聲明。沒(méi)有使用extends關(guān)鍵字的類(lèi)聲明被稱(chēng)作基類(lèi):

"use strict";

// Food 是一個(gè)基類(lèi)
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`
    }

    print () {
        console.log( this.toString() );
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);

chicken_breast.print(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'
console.log(chicken_breast.protein); // 26 (LINE A)

需要注意到以下事情:

  • 類(lèi)只能包含方法定義,不能有數(shù)據(jù)屬性;
  • 定義方法時(shí),可以使用簡(jiǎn)寫(xiě)方法定義;
  • 與創(chuàng)建對(duì)象不同,我們不能在類(lèi)主體中使用逗號(hào)分隔方法定義;
  • 我們可以在實(shí)例化對(duì)象上直接引用類(lèi)的屬性(如 LINE A)。

類(lèi)有一個(gè)獨(dú)有的特性,就是 contructor 構(gòu)造方法。在構(gòu)造方法中我們可以初始化對(duì)象的屬性。
構(gòu)造方法的定義并不是必須的。如果不寫(xiě)構(gòu)造方法,引擎會(huì)為我們插入一個(gè)空的構(gòu)造方法

"use strict";

class NoConstructor {
    /* JavaScript 會(huì)插入這樣的代碼:
     constructor () { }
    */
}

const nemo = new NoConstructor(); // 能工作,但沒(méi)啥意思

將一個(gè)類(lèi)賦值給一個(gè)變量的形式叫類(lèi)表達(dá)式,這種寫(xiě)法可以替代上面的語(yǔ)法形式:

"use strict";

// 這是一個(gè)匿名類(lèi)表達(dá)式,在類(lèi)主體中我們不能通過(guò)名稱(chēng)引用它
const Food = class {
    // 和上面一樣的類(lèi)定義……
}

// 這是一個(gè)命名類(lèi)表達(dá)式,在類(lèi)主體中我們可以通過(guò)名稱(chēng)引用它
const Food = class FoodClass {
    // 和上面一樣的類(lèi)定義……

    //  添加一個(gè)新方法,證明我們可以通過(guò)內(nèi)部名稱(chēng)引用 FoodClass……        
    printMacronutrients () {
        console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
chicken_breast.printMacronutrients(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'

// 但是不能在外部引用
try {
    console.log(FoodClass.protein); // 引用錯(cuò)誤
} catch (err) {
    // pass
}

這一行為與匿名函數(shù)與命名函數(shù)表達(dá)式很類(lèi)似。

使用extends創(chuàng)建子類(lèi)以及使用super調(diào)用

使用extends創(chuàng)建的類(lèi)被稱(chēng)作子類(lèi),或派生類(lèi)。這一用法簡(jiǎn)單明了,我們直接在上面的例子中構(gòu)建:

"use strict";

// FatFreeFood 是一個(gè)派生類(lèi)
class FatFreeFood extends Food {

    constructor (name, protein, carbs) {
        super(name, protein, carbs, 0);
    }

    print () {
        super.print();
        console.log(`Would you look at that -- ${this.name} has no fat!`);
    }

}

const fat_free_yogurt = new FatFreeFood('Greek Yogurt', 16, 12);
fat_free_yogurt.print(); // 'Greek Yogurt | 26g P :: 16g C :: 0g F  /  Would you look at that -- Greek Yogurt has no fat!'

派生類(lèi)擁有我們上文討論的一切有關(guān)基類(lèi)的特性,另外還有如下幾點(diǎn)新特點(diǎn):

  • 子類(lèi)使用class關(guān)鍵字聲明,之后緊跟一個(gè)標(biāo)識(shí)符,然后使用extend關(guān)鍵字,最后寫(xiě)一個(gè)任意表達(dá)式。這個(gè)表達(dá)式通常來(lái)講就是個(gè)標(biāo)識(shí)符,但理論上也可以是函數(shù)。
  • 如果你的派生類(lèi)需要引用它的父類(lèi),可以使用super關(guān)鍵字。
  • 一個(gè)派生類(lèi)不能有一個(gè)空的構(gòu)造函數(shù)。即使這個(gè)構(gòu)造函數(shù)就是調(diào)用了一下super(),你也得把它顯式的寫(xiě)出來(lái)。但派生類(lèi)卻可以沒(méi)有構(gòu)造函數(shù)。
  • 在派生類(lèi)的構(gòu)造函數(shù)中,必須先調(diào)用super,才能使用this關(guān)鍵字(譯者注:僅在構(gòu)造函數(shù)中是這樣,在其他方法中可以直接使用this)。
    在JavaScript中僅有兩個(gè)super關(guān)鍵字的使用場(chǎng)景:
  • 在子類(lèi)構(gòu)造函數(shù)中調(diào)用。如果初始化派生類(lèi)是需要使用父類(lèi)的構(gòu)造函數(shù),我們可以在子類(lèi)的構(gòu)造函數(shù)中調(diào)用super(parentConstructorParams),傳遞任意需要的參數(shù)。
  • 引用父類(lèi)的方法。在常規(guī)方法定義中,派生類(lèi)可以使用點(diǎn)運(yùn)算符來(lái)引用父類(lèi)的方法:super.methodName。
    我們的 FatFreeFood 演示了這兩種情況:
  • 在構(gòu)造函數(shù)中,我們簡(jiǎn)單的調(diào)用了super,并將脂肪的量傳入為0。
  • 在我們的print方法中,我們先調(diào)用了super.print,之后才添加了其他的邏輯。
最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 面向?qū)ο蟮恼Z(yǔ)言都有一個(gè)類(lèi)的概念,通過(guò)類(lèi)可以創(chuàng)建多個(gè)具有相同方法和屬性的對(duì)象,ES6之前并沒(méi)有類(lèi)的概念,在ES6中引...
    Erric_Zhang閱讀 1,206評(píng)論 1 4
  • class的基本用法 概述 JavaScript語(yǔ)言的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù),定義并生成新對(duì)象。下面是一個(gè)例子: ...
    呼呼哥閱讀 4,194評(píng)論 3 11
  • 假如我們想要?jiǎng)?chuàng)建一個(gè)經(jīng)典的面向?qū)ο笤O(shè)計(jì)示例:Circle類(lèi)。想象一下我們正在為一個(gè)簡(jiǎn)單的Canvas庫(kù)編寫(xiě)這個(gè)Ci...
    糖心m閱讀 435評(píng)論 0 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,623評(píng)論 18 399
  • 獲取信息 落榜的,在讀的,瀘職院,品牌營(yíng)銷(xiāo)知曉度。 途徑:QQ,熟人,招人。運(yùn)營(yíng)好QQ,裂變,增強(qiáng)粘性。網(wǎng)絡(luò)搜索 ...
    問(wèn)學(xué)行者閱讀 192評(píng)論 0 0

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