深入理解JavaScript和TypeScript中的class
前言
對于一個前端開發(fā)者來說,很少用到 class ,因為在 JavaScript 中更多的是 函數(shù)式 編程,抬手就是一個 function,幾乎不見 class 或 new 的蹤影。所以 設(shè)計模式 也是大多數(shù)前端開發(fā)者的一個短板。
最近在學(xué)習(xí) Angular 的過程中發(fā)現(xiàn)其大量的運用了 class,不得不佩服,Angular 確實是一個優(yōu)秀的、值得深入研究的 框架。
本文將簡單的介紹一下 JavaScript 和 TypeScript 中的 class。
基本概念
在介紹 class 之前,要先介紹一些基本的概念。
1、靜態(tài)成員
類自身的成員,可以繼承,但實例無法訪問,一般多見于工具類,比如在jQuery時代最常見的 $.ajax ,ajax 便是 $ 的靜態(tài)方法,使用方便,不需要再通過 new 或者函數(shù)調(diào)用的得到一個新實例。
2、私有成員
類內(nèi)部的成員,一般是不能繼承的,只能在內(nèi)部使用,實例無法訪問,有一點點像閉包內(nèi)部的變量,但是還是一定的差別,目前 JavaScript 無法直接定義私有成員,只能通過其它方式輔助實現(xiàn)。
3、getter/setter
存取器屬性,當(dāng)我們訪問或者修改一個實例的屬性的時候,我們可通過存取器屬性攔截這兩個操作,從而做一些其它的事情,vue正是通過這個api來實現(xiàn)對數(shù)據(jù)變化的追蹤。
4、實例成員
指 new 出來的實例所具有的成員,可以被繼承,也是通過這個特性實現(xiàn)了代碼的復(fù)用。
5、抽象類,抽象方法
抽象類指不可以被實例化的類,通過 new 關(guān)鍵字調(diào)用會報錯,一般都被設(shè)計成父類。
抽象方法,只提供方法的名稱,參數(shù)和返回值,不負(fù)責(zé)實現(xiàn),具體的實現(xiàn)由子類去完成,如果一個子類繼承于抽象類,那么這個子類必須實現(xiàn)父類所有的抽象方法,否則會報錯。
這兩個概念在 JavaScript 都無法直接實現(xiàn),但在 TypeScript 或 其它面向?qū)ο笳Z言中可以輕松實現(xiàn),另外這個特性也是用于實現(xiàn) 多態(tài) 的重要手段。
案例介紹
為了更好的介紹 class,本文將采用三個 類 來做例子,分別是 Person、Chinese、American。從字面上可以很快的知道: Person 是 父類(基類) ,Chinese 和 American 是 子類(派生類) 。
Person 有 name、age、gender 三個屬性,sayHello 方法和 fullName 存取器屬性。同時 Person 還有一些 靜態(tài)成員 和 私有成員 ,由于實在太難想例子了,所以就用 foo、bar、x、y、z 這些來代替吧。
作為子類的 Chinese 和 American 繼承了 Person 的實例成員和靜態(tài)成員。同時它們自身也有一些自己的方法和屬性:
Chinese 有 kungfu 屬性,會習(xí)武 martial。
American 有 twitter,還可以 sendTwitter。
接下來我們就分別使用 JavaScript 和 TypeScript 來實現(xiàn)這個案例。
JavaScript 中的 class
JavaScript 中的 class 要分開說,在 ES6 中提供了兩個關(guān)鍵字 class 和 extends ,雖然它們只是語法糖,底層還是再利用 prototype 實現(xiàn)繼承的,但是不能否認(rèn),這中寫法確實讓代碼更清晰,更易讀。
ES6 中的 class
class Person { // #x = '私有屬性x'; // static x = '靜態(tài)屬性x'; // name; // age; // gender; // 上面的寫法還在提案中,并沒有成為正式標(biāo)準(zhǔn),不過變化的可能性已經(jīng)不大了。 // 順便吐槽一下,用 # 表示私有成員,真的是很無語. /** * Person的靜態(tài)方法,可以被子類繼承 * 可以通過 this 訪問靜態(tài)成員 */ static foo() { console.log(`類 ${this.name} 有一個 ${this.x}`); } constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } /** * 數(shù)據(jù)存儲器,可以訪問實例成員,子類的實例可以繼承 * 以通過 this 訪問實例成員 */ get fullName() { const suffix = this.gender === '男' ? '先生' : '女士'; return this.name + suffix; } set fullName(value) { console.log(`你已改名為 ${value} `); } /** * Person的實例方法,可以被子類的實例繼承 * 可以通過 this 訪問實例成員 */ sayHello() { console.log(`你好我是 ${this.fullName} ,我 ${this.age} 歲了`); } } Person.x = '靜態(tài)屬性x';
class Chinese extends Person { static bar() { console.log(`類 ${this.name} 的父類是 ${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(`類 ${this.name} 有自己的 ${this.y} ,還繼承了父類 ${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)屬性x Person.foo(); // 類 Person 有一個 靜態(tài)屬性x Chinese.x; // 靜態(tài)屬性x Chinese.foo(); // 類 Chinese 有一個 靜態(tài)屬性x Chinese.bar(); // 類 Chinese 的父類是 Person American.x; // 靜態(tài)屬性x American.y; // '靜態(tài)屬性y American.foo(); // 類 American 有一個 靜態(tài)屬性x American.bar(); // 類 American 有自己的 靜態(tài)屬性y ,還繼承了父類 Person 的 靜態(tài)屬性x const 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('推特治國'); // 特朗普 : 推特治國
ES6 之前的 class
ES5 的繼承,實質(zhì)是先創(chuàng)造子類的實例對象 this,
然后再將父類的方法添加到 this 上面 Parent.apply(this) 。
ES6 的繼承機制完全不同,實質(zhì)是先創(chuàng)造父類的實例對象 this,所以必須先調(diào)用 super 方法,
然后再用子類的構(gòu)造函數(shù)修改this。
為了實現(xiàn)繼承,我們需要先實現(xiàn)一個 extendsClass 函數(shù),它的作用是讓子類繼承父類的靜態(tài)成員和實例成員。
function extendsClass(parent, child) { // 防止子類和父類相同名稱的成員被父類覆蓋 var flag = false; // 繼承靜態(tài)成員 for (var k in parent) { flag = k in child; if (!flag) { child[k] = parent[k]; } } // 繼承父類prototype上的成員 // 用一個新的構(gòu)造函數(shù)切斷父類和子類之間的數(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 寫在 prototype 會獲取不到 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('類 ' + this.name + ' 有一個 ' + 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指向,實現(xiàn)繼承父類的實例屬性 Person.call(this, name, age, gender); this.kungfu = kungfu; } Chinese.bar = function () { console.log('類 ' + this.name + ' 的父類是 ' + 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('類 ' + this.name + ' 有自己的 ' + this.y + ' ,還繼承了父類 ' + Person.name + ' 的 ' + Person.x); } American.prototype = { constructor: American, sendTwitter: function (msg) { console.log(this.name + ' : '); console.log(' ' + msg); } }; extendsClass(Person, American);
TypeScript 中的 class
講完了 JavaScript 中的類,還是沒有用到 抽象類,抽象方法,私有方法這三個概念,由于 JavaScript 語言的局限性,想要實現(xiàn)這三種概念是很困難的,但是在 TypeScript 可以輕松的實現(xiàn)這一特性。
首先我們稍微修改一下例子中的描述,Person 是抽象類,因為一個正常的人肯定是有國籍的,Person 的 sayHello 方法是抽象方法,因為每個國家打招呼的方式不一樣。另外一個人的性別是只能讀取,不能修改的,且是確定的是,不是男生就是女生,所以還要借助一下枚舉。
enum Gender { female = 0, male = 1 };
abstract class Person { private x: string = '私有屬性x,子類和實例都無法訪問'; protected y: string = '私有屬性y,子類可以訪問,實例無法訪問'; name: string; public age: number; public readonly gender: Gender; // 用關(guān)鍵字 readonly 表明這是一個只讀屬性 public static x: string = '靜態(tài)屬性x'; public static foo() { console.log(`類 ${this.name} 有一個 ${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} `); } // 抽象方法,具體實現(xiàn)交由子類完成 abstract sayHello(): void; }
class Chinese extends Person { public kungfu: string; public static bar() { console.log(`類 ${this.name} 的父類是 ${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(`類 ${this.name} 有自己的 ${this.y} ,還繼承了父類 ${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)屬性x Person.foo(); // 類 Person 有一個 靜態(tài)屬性x Chinese.x; // 靜態(tài)屬性x Chinese.foo(); // 類 Chinese 有一個 靜態(tài)屬性x Chinese.bar(); // 類 Chinese 的父類是 Person American.x; // 靜態(tài)屬性x American.y; // '靜態(tài)屬性y American.foo(); // 類 American 有一個 靜態(tài)屬性x American.bar(); // 類 American 有自己的 靜態(tài)屬性y ,還繼承了父類 Person 的 靜態(tài)屬性x const 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 old a.sendTwitter('推特治國'); // 特朗普 : 推特治國
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
JavaScript的History API使搜索引擎抓取AJAX內(nèi)容
這篇文章主要介紹了JavaScript的History API使搜索引擎抓取AJAX內(nèi)容 的相關(guān)資料,需要的朋友可以參考下2015-12-12微信小程序頁面與組件之間信息傳遞與函數(shù)調(diào)用
不管是vue還是react中,都在強調(diào)組件思想,所以下面這篇文章主要給大家介紹了關(guān)于微信小程序頁面與組件之間信息傳遞與函數(shù)調(diào)用的相關(guān)資料,需要的朋友可以參考下2021-05-05微信小程序?qū)崿F(xiàn)的數(shù)字滑塊拼圖效果
滑塊拼圖(Slider?Puzzle)是一種經(jīng)典的智力游戲,通常由一個3x3或更大的格子組成,其中一個格子為空,玩家通過滑動拼圖塊來達到特定的圖案或順序,這篇文章主要介紹了微信小程序?qū)崿F(xiàn)的數(shù)字滑塊拼圖,需要的朋友可以參考下2024-08-08