前言
對(duì)于一個(gè)前端開(kāi)發(fā)者來(lái)說(shuō),很少用到 class ,因?yàn)樵?JavaScript 中更多的是 函數(shù)式 編程,抬手就是一個(gè) function,幾乎不見(jiàn) class 或 new 的蹤影。所以 設(shè)計(jì)模式 也是大多數(shù)前端開(kāi)發(fā)者的一個(gè)短板。
最近在學(xué)習(xí) Angular 的過(guò)程中發(fā)現(xiàn)其大量的運(yùn)用了 class,不得不佩服,Angular 確實(shí)是一個(gè)優(yōu)秀的、值得深入研究的 框架。
本文將簡(jiǎn)單的介紹一下 JavaScript 和 TypeScript 中的 class。
基本概念
在介紹 class 之前,要先介紹一些基本的概念。
1、靜態(tài)成員
類(lèi)自身的成員,可以繼承,但實(shí)例無(wú)法訪問(wèn),一般多見(jiàn)于工具類(lèi),比如在jQuery時(shí)代最常見(jiàn)的 $.ajax ,ajax 便是 $ 的靜態(tài)方法,使用方便,不需要再通過(guò) new 或者函數(shù)調(diào)用的得到一個(gè)新實(shí)例。
2、私有成員
類(lèi)內(nèi)部的成員,一般是不能繼承的,只能在內(nèi)部使用,實(shí)例無(wú)法訪問(wèn),有一點(diǎn)點(diǎn)像閉包內(nèi)部的變量,但是還是一定的差別,目前 JavaScript 無(wú)法直接定義私有成員,只能通過(guò)其它方式輔助實(shí)現(xiàn)。
3、getter/setter
存取器屬性,當(dāng)我們?cè)L問(wèn)或者修改一個(gè)實(shí)例的屬性的時(shí)候,我們可通過(guò)存取器屬性攔截這兩個(gè)操作,從而做一些其它的事情,vue正是通過(guò)這個(gè)api來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)變化的追蹤。
4、實(shí)例成員
指 new 出來(lái)的實(shí)例所具有的成員,可以被繼承,也是通過(guò)這個(gè)特性實(shí)現(xiàn)了代碼的復(fù)用。
5、抽象類(lèi),抽象方法
抽象類(lèi)指不可以被實(shí)例化的類(lèi),通過(guò) new 關(guān)鍵字調(diào)用會(huì)報(bào)錯(cuò),一般都被設(shè)計(jì)成父類(lèi)。
抽象方法,只提供方法的名稱(chēng),參數(shù)和返回值,不負(fù)責(zé)實(shí)現(xiàn),具體的實(shí)現(xiàn)由子類(lèi)去完成,如果一個(gè)子類(lèi)繼承于抽象類(lèi),那么這個(gè)子類(lèi)必須實(shí)現(xiàn)父類(lèi)所有的抽象方法,否則會(huì)報(bào)錯(cuò)。
這兩個(gè)概念在 JavaScript 都無(wú)法直接實(shí)現(xiàn),但在 TypeScript 或 其它面向?qū)ο笳Z(yǔ)言中可以輕松實(shí)現(xiàn),另外這個(gè)特性也是用于實(shí)現(xiàn) 多態(tài) 的重要手段。
案例介紹
為了更好的介紹 class,本文將采用三個(gè) 類(lèi) 來(lái)做例子,分別是 Person、Chinese、American。從字面上可以很快的知道: Person 是 父類(lèi)(基類(lèi)) ,Chinese 和 American 是 子類(lèi)(派生類(lèi)) 。
Person 有 name、age、gender 三個(gè)屬性,sayHello 方法和 fullName 存取器屬性。同時(shí) Person 還有一些 靜態(tài)成員 和 私有成員 ,由于實(shí)在太難想例子了,所以就用 foo、bar、x、y、z 這些來(lái)代替吧。
作為子類(lèi)的 Chinese 和 American 繼承了 Person 的實(shí)例成員和靜態(tài)成員。同時(shí)它們自身也有一些自己的方法和屬性:
Chinese 有 kungfu 屬性,會(huì)習(xí)武 martial。
American 有 twitter,還可以 sendTwitter。
接下來(lái)我們就分別使用 JavaScript 和 TypeScript 來(lái)實(shí)現(xiàn)這個(gè)案例。
JavaScript 中的 class
JavaScript 中的 class 要分開(kāi)說(shuō),在 ES6 中提供了兩個(gè)關(guān)鍵字 class 和 extends ,雖然它們只是語(yǔ)法糖,底層還是再利用 prototype 實(shí)現(xiàn)繼承的,但是不能否認(rèn),這中寫(xiě)法確實(shí)讓代碼更清晰,更易讀。
ES6 中的 class
class Person { // #x = '私有屬性x'; // static x = '靜態(tài)屬性x'; // name; // age; // gender; // 上面的寫(xiě)法還在提案中,并沒(méi)有成為正式標(biāo)準(zhǔn),不過(guò)變化的可能性已經(jīng)不大了。 // 順便吐槽一下,用 # 表示私有成員,真的是很無(wú)語(yǔ). /** * Person的靜態(tài)方法,可以被子類(lèi)繼承 * 可以通過(guò) this 訪問(wèn)靜態(tài)成員 */ static foo() { console.log(`類(lèi) ${this.name} 有一個(gè) ${this.x}`); } constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } /** * 數(shù)據(jù)存儲(chǔ)器,可以訪問(wèn)實(shí)例成員,子類(lèi)的實(shí)例可以繼承 * 以通過(guò) this 訪問(wèn)實(shí)例成員 */ get fullName() { const suffix = this.gender === '男' ? '先生' : '女士'; return this.name + suffix; } set fullName(value) { console.log(`你已改名為 ${value} `); } /** * Person的實(shí)例方法,可以被子類(lèi)的實(shí)例繼承 * 可以通過(guò) this 訪問(wèn)實(shí)例成員 */ sayHello() { console.log(`你好我是 ${this.fullName} ,我 ${this.age} 歲了`); }}Person.x = '靜態(tài)屬性x';class Chinese extends Person { static bar() { console.log(`類(lèi) ${this.name} 的父類(lèi)是 ${super.name}`); super.foo(); } constructor(name, age, gender, kungfu) { super(name, age, gender); this.kungfu = kungfu; } martial() { console.log(`${this.name} 正在修煉 ${this.kungfu} `); }}class American extends Person { // static y = '靜態(tài)屬性y'; static bar() { console.log(`類(lèi) ${this.name} 有自己的 ${this.y} ,還繼承了父類(lèi) ${super.name} 的 ${super.x}`); } constructor(name, age, gender, twitter) { super(name, age, gender); this.twitter = twitter; } sendTwitter(msg) { console.log(`${this.name} : `); console.log(` ${msg}`); }}American.y = '靜態(tài)屬性y';Person.x; // 靜態(tài)屬性xPerson.foo(); // 類(lèi) Person 有一個(gè) 靜態(tài)屬性xChinese.x; // 靜態(tài)屬性xChinese.foo(); // 類(lèi) Chinese 有一個(gè) 靜態(tài)屬性xChinese.bar(); // 類(lèi) Chinese 的父類(lèi)是 PersonAmerican.x; // 靜態(tài)屬性xAmerican.y; // '靜態(tài)屬性yAmerican.foo(); // 類(lèi) American 有一個(gè) 靜態(tài)屬性xAmerican.bar(); // 類(lèi) American 有自己的 靜態(tài)屬性y ,還繼承了父類(lèi) Person 的 靜態(tài)屬性xconst p = new Person('Lucy', 20, '女');const c = new Chinese('韓梅梅', 18, '女', '詠春拳');const a = new American('特朗普', 72, '男', 'Donald J. Trump');c.sayHello(); // 你好我是 韓梅梅女士 ,我 18 歲了c.martial(); // 韓梅梅 正在修煉 詠春拳 a.sayHello(); // 你好我是 特朗普先生 ,我 72 歲了a.sendTwitter('推特治國(guó)'); // 特朗普 : 推特治國(guó)ES6 之前的 class
ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類(lèi)的實(shí)例對(duì)象 this,
然后再將父類(lèi)的方法添加到 this 上面 Parent.apply(this) 。
ES6 的繼承機(jī)制完全不同,實(shí)質(zhì)是先創(chuàng)造父類(lèi)的實(shí)例對(duì)象 this,所以必須先調(diào)用 super 方法,
然后再用子類(lèi)的構(gòu)造函數(shù)修改this。
為了實(shí)現(xiàn)繼承,我們需要先實(shí)現(xiàn)一個(gè) extendsClass 函數(shù),它的作用是讓子類(lèi)繼承父類(lèi)的靜態(tài)成員和實(shí)例成員。
function extendsClass(parent, child) { // 防止子類(lèi)和父類(lèi)相同名稱(chēng)的成員被父類(lèi)覆蓋 var flag = false; // 繼承靜態(tài)成員 for (var k in parent) { flag = k in child; if (!flag) { child[k] = parent[k]; } } // 繼承父類(lèi)prototype上的成員 // 用一個(gè)新的構(gòu)造函數(shù)切斷父類(lèi)和子類(lèi)之間的數(shù)據(jù)共享 var F = function () { } F.prototype = parent.prototype; var o = new F(); for (var k in o) { flag = k in child.prototype; if (!flag) { child.prototype[k] = o[k]; } }}function Person(name, age, gender) { this.name = name; this.age = age; this.gender = this.gender; // 如果將 getter/setter 寫(xiě)在 prototype 會(huì)獲取不到 Object.defineProperty(this, 'fullName', { get: function () { var suffix = this.gender === '男' ? '先生' : '女士'; return this.name + suffix; }, set: function () { console.log('你已改名為 ' + value + ' '); }, });}Person.x = '靜態(tài)屬性x';Person.foo = function () { console.log('類(lèi) ' + this.name + ' 有一個(gè) ' + this.x);}Person.prototype = { constructor: Person, // get fullName() { }, // set fullName(value) { }, sayHello: function () { console.log('你好我是 ' + this.fullName + ' ,我 ' + this.age + ' 了'); },};function Chinese(name, age, gender, kungfu) { // 用call改變this指向,實(shí)現(xiàn)繼承父類(lèi)的實(shí)例屬性 Person.call(this, name, age, gender); this.kungfu = kungfu;}Chinese.bar = function () { console.log('類(lèi) ' + this.name + ' 的父類(lèi)是 ' + Person.name); Person.foo();}Chinese.prototype = { constructor: Chinese, martial: function () { console.log(this.name + ' 正在修煉 ' + this.kungfu + ' '); }};extendsClass(Person, Chinese);function American(name, age, gender, twitter) { Person.call(this, name, age, gender); this.twitter = twitter;}American.y = '靜態(tài)屬性y';American.bar = function () { console.log('類(lèi) ' + this.name + ' 有自己的 ' + this.y + ' ,還繼承了父類(lèi) ' + Person.name + ' 的 ' + Person.x);}American.prototype = { constructor: American, sendTwitter: function (msg) { console.log(this.name + ' : '); console.log(' ' + msg); }};extendsClass(Person, American);TypeScript 中的 class
講完了 JavaScript 中的類(lèi),還是沒(méi)有用到 抽象類(lèi),抽象方法,私有方法這三個(gè)概念,由于 JavaScript 語(yǔ)言的局限性,想要實(shí)現(xiàn)這三種概念是很困難的,但是在 TypeScript 可以輕松的實(shí)現(xiàn)這一特性。
首先我們稍微修改一下例子中的描述,Person 是抽象類(lèi),因?yàn)橐粋€(gè)正常的人肯定是有國(guó)籍的,Person 的 sayHello 方法是抽象方法,因?yàn)槊總€(gè)國(guó)家打招呼的方式不一樣。另外一個(gè)人的性別是只能讀取,不能修改的,且是確定的是,不是男生就是女生,所以還要借助一下枚舉。
enum Gender { female = 0, male = 1};abstract class Person { private x: string = '私有屬性x,子類(lèi)和實(shí)例都無(wú)法訪問(wèn)'; protected y: string = '私有屬性y,子類(lèi)可以訪問(wèn),實(shí)例無(wú)法訪問(wèn)'; name: string; public age: number; public readonly gender: Gender; // 用關(guān)鍵字 readonly 表明這是一個(gè)只讀屬性 public static x: string = '靜態(tài)屬性x'; public static foo() { console.log(`類(lèi) ${this.name} 有一個(gè) ${this.x}`); } constructor(name: string, age: number, gender: Gender) { this.name = name; this.age = age; this.gender = gender; } get fullName(): string { const suffix = this.gender === 1 ? '先生' : '女士'; return this.name + suffix; } set FullName(value: string) { console.log(`你已改名為 ${value} `); } // 抽象方法,具體實(shí)現(xiàn)交由子類(lèi)完成 abstract sayHello(): void;}class Chinese extends Person { public kungfu: string; public static bar() { console.log(`類(lèi) ${this.name} 的父類(lèi)是 ${super.name}`); super.foo(); } public constructor(name: string, age: number, gender: Gender, kungfu: string) { super(name, age, gender); this.kungfu = kungfu; } public sayHello(): void { console.log(`你好我是 ${this.fullName} ,我 ${this.age} 歲了`); } public martial() { console.log(`${this.name} 正在修煉 ${this.kungfu} `); }}class American extends Person { static y = '靜態(tài)屬性y'; public static bar() { console.log(`類(lèi) ${this.name} 有自己的 ${this.y} ,還繼承了父類(lèi) ${super.name} 的 ${super.x}`); } public twitter: string; public constructor(name: string, age: number, gender: Gender, twitter: string) { super(name, age, gender); this.twitter = twitter; } public sayHello(): void { console.log(`Hello, I am ${this.fullName} , I'm ${this.age} years old`); } public sendTwitter(msg: string): void { console.log(`${this.name} : `); console.log(` ${msg}`); }}Person.x; // 靜態(tài)屬性xPerson.foo(); // 類(lèi) Person 有一個(gè) 靜態(tài)屬性xChinese.x; // 靜態(tài)屬性xChinese.foo(); // 類(lèi) Chinese 有一個(gè) 靜態(tài)屬性xChinese.bar(); // 類(lèi) Chinese 的父類(lèi)是 PersonAmerican.x; // 靜態(tài)屬性xAmerican.y; // '靜態(tài)屬性yAmerican.foo(); // 類(lèi) American 有一個(gè) 靜態(tài)屬性xAmerican.bar(); // 類(lèi) American 有自己的 靜態(tài)屬性y ,還繼承了父類(lèi) Person 的 靜態(tài)屬性xconst c: Chinese = new Chinese('韓梅梅', 18, Gender.female, '詠春拳');const a: American = new American('特朗普', 72, Gender.male, 'Donald J. Trump');c.sayHello(); // 你好我是 韓梅梅女士 ,我 18 歲了c.martial(); // 韓梅梅 正在修煉 詠春拳 a.sayHello(); // Hello, I am 特朗普先生 , I'm 72 years olda.sendTwitter('推特治國(guó)'); // 特朗普 : 推特治國(guó)總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)武林網(wǎng)的支持。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注