JS高級(jí)程序設(shè)計(jì)之class繼承重點(diǎn)詳解
引言
前文已提過:在 class 出現(xiàn)之前,JavaScript 實(shí)現(xiàn)繼承是件麻煩事,構(gòu)造函數(shù)繼承有加上原型上的函數(shù)不能復(fù)用的問題;原型鏈繼承又存在引用值屬性的修改不獨(dú)立的問題;組合繼承又存在兩次調(diào)用構(gòu)造函數(shù)的問題,寄生組合繼承,寫起來(lái)又太麻煩了,總之,在 class 出現(xiàn)前,JavaScipt 實(shí)現(xiàn)繼承真是件麻煩事兒。
然而,class 的出現(xiàn)真的改變這一現(xiàn)狀了嗎?
不如往下看。
寫法
與函數(shù)類型相似,定義類也有兩種主要方式:類聲明和類表達(dá)式。
// 類聲明 class Person {}
// 類表達(dá)式 const Animal = class {};
不過,與函數(shù)定義不同的是,雖然函數(shù)聲明可以提升,但類定義不能。
與函數(shù)構(gòu)造函數(shù)一樣,多數(shù)編程風(fēng)格都建議類名的首字母要大寫,以區(qū)別于通過它創(chuàng)建的實(shí)例。
類可以包含:
- 構(gòu)造函數(shù)方法
- 實(shí)例方法
- 獲取函數(shù)
- 設(shè)置函數(shù)
- 靜態(tài)類方法
這些項(xiàng)都是可選的
constructor
class Person {
constructor(name) {
this.name = name
console.log('person ctor');
}
}
let p1 = new Person("p1")
constructor 會(huì)告訴解釋器 在使用 new 操作符創(chuàng)建類的新實(shí)例時(shí),應(yīng)該調(diào)用這個(gè)函數(shù)。
等同于
function Person(name){
this.name = name
console.log('person ctor')
}
let p1 = new Person("p1")
類構(gòu)造函數(shù)與構(gòu)造函數(shù)的主要區(qū)別是,這樣寫會(huì)報(bào)錯(cuò):
class Animal {}
let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'
所以,new 操作符是強(qiáng)制要寫的;
使用 new 時(shí),原理與 new 一個(gè)對(duì)象也是一樣的,因?yàn)樘匾?,再?gòu)?qiáng)調(diào)一遍:
(1) 在內(nèi)存中創(chuàng)建一個(gè)新對(duì)象。
(2) 這個(gè)新對(duì)象內(nèi)部的[[Prototype]]指針被賦值為構(gòu)造函數(shù)的 prototype 屬性。
(3) 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個(gè)新對(duì)象(即 this 指向新對(duì)象)。
(4) 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對(duì)象添加屬性)。
(5) 如果構(gòu)造函數(shù)返回非空對(duì)象,則返回該對(duì)象;否則,返回剛創(chuàng)建的新對(duì)象。
特性
從各方面來(lái)看,ECMAScript 類就是一種特殊函數(shù)。
我們可以用 typeof 打印試試:
class Person {}
console.log(typeof Person); // function
也可以用 instanceof 檢查它的原型鏈
class Person {}
let p = new Person()
console.log(p instanceof Person); // true
通過 class 構(gòu)造的每個(gè)實(shí)例都對(duì)應(yīng)一個(gè)唯一的成員對(duì)象,這意味著所有成員都不會(huì)在原型上共享;
class Person {
constructor() {
this.name = new String('Jack');
this.sayName = () => console.log(this.name);
}
}
let p1 = new Person();
let p2 = new Person();
console.log(p1.name === p2.name) // false
console.log(p1.sayName === p2.sayName) // false
如果想要共享,就改寫成方法,寫在 constructor 外面:
class Person {
constructor() {
this.name = new String('Jack');
}
sayName(){
console.log(this.name);
}
}
let p1 = new Person();
let p2 = new Person();
console.log(p1.sayName === p2.sayName) // true
我們可以在方法前面加 static 關(guān)鍵字,實(shí)現(xiàn):靜態(tài)類成員。我們不能在類的實(shí)例上調(diào)用靜態(tài)方法,只能通過類本身調(diào)用。不做贅述。
繼承
ECMAScript 6 新增特性中最出色的一個(gè)就是原生支持了類繼承機(jī)制。雖然類繼承使用的是新語(yǔ)法,但背后依舊使用的是原型鏈。
讓我們?cè)倩仡櫂?gòu)造函數(shù)繼承和原型鏈繼承 2 個(gè)經(jīng)典的問題:
① 構(gòu)造函數(shù)繼承的問題:構(gòu)造函數(shù)外在原型上定義方法,不能重用
function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}
function SubType(){
SuperType.call(this) // 構(gòu)造函數(shù)繼承
}
let p1 = new SubType()
console.log(p1.sayName()) // Uncaught TypeError: p1.sayName is not a function
而原型鏈繼承可以解決這一點(diǎn):
function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}
function SubType(){}
SubType.prototype = new SuperType() // 原型鏈繼承
let p1 = new SubType()
console.log(p1.sayName()) // bob
② 原型鏈繼承的問題:原型中包含的引用值會(huì)在所有實(shí)例間共享。
function SuperType(){
this.name = ["bob","tom"];
}
function SubType(){}
SubType.prototype = new SuperType() // 原型鏈繼承
let p1 = new SubType()
p1.name.push("jerry")
let p2 = new SubType()
console.log(p2.name) // ['bob', 'tom', 'jerry']
而構(gòu)造函數(shù)繼承可以解決這一點(diǎn):
function SuperType(){
this.name = ["bob","tom"];
}
function SubType(){
SuperType.call(this) // 構(gòu)造函數(shù)繼承
}
let p1 = new SubType()
p1.name.push("jerry")
let p2 = new SubType()
console.log(p2.name) // ['bob', 'tom']
class 繼承有這兩個(gè)問題嗎??
代碼一試便知:
class SuperType{}
SuperType.prototype.sayName = ()=>{console.log("bob")}
class SubType extends SuperType{
}
let p1 = new SubType()
p1.sayName() // bob
問題①,沒有問題,在構(gòu)造函數(shù)外寫的原型繼承,公共方法還是能訪問的??!
class SuperType{
constructor(){
this.name=["bob","tom"]
}
}
class SubType extends SuperType{
}
let p1 = new SubType()
let p2 = new SubType()
p1.name.push("Jerry")
console.log(p2.name) // ['bob', 'tom']
問題②,沒有問題,在 constructor 的引用值屬性,修改不會(huì)產(chǎn)生干涉!!
class 繼承完美的解決了構(gòu)造函數(shù)繼承的問題,和原型鏈繼承的問題,寫起來(lái)也沒有組合繼承、寄生繼承那么麻煩,如果非得用 JS 模擬面向?qū)ο缶幊蹋琧lass 必不可少?。?/p>
題外話
其實(shí)寫 Class C 和 C.prototype 一起寫是很危險(xiǎn)的:明明都在操作面向?qū)ο蟮念惲?,還要操作原型鏈。類操作和原型操作是兩種不同的設(shè)計(jì)思路,有興趣可見本瓜一年前的一篇文章:“類”設(shè)計(jì)模式和“原型”設(shè)計(jì)模式——“復(fù)制”和“委托”的差異
以上就是JS高級(jí)程序設(shè)計(jì)之class繼承重點(diǎn)詳解的詳細(xì)內(nèi)容,更多關(guān)于JS高級(jí)程序設(shè)計(jì)class繼承的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用js實(shí)現(xiàn)簡(jiǎn)單開關(guān)燈代碼
這篇文章主要分享的是如何利用js實(shí)現(xiàn)簡(jiǎn)單開關(guān)燈代碼,下面文字圍繞js實(shí)現(xiàn)簡(jiǎn)單開關(guān)燈的相關(guān)資料展開具體內(nèi)容,需要的朋友可以參考以下,希望對(duì)大家又所幫助2021-11-11
原型和原型鏈 prototype和proto的區(qū)別詳情
原型是function對(duì)象下的屬性,它定義了構(gòu)造函數(shù)的共同祖先,也就是一個(gè)父子級(jí)的關(guān)系,子對(duì)象會(huì)繼承父對(duì)象的方法和屬性,每個(gè)實(shí)例對(duì)象下都有__proto__屬性,通過屬性__proto__指向構(gòu)造函數(shù)的原型對(duì)象,當(dāng)?shù)竭_(dá)末端時(shí),返回null,這樣一層一層向頂端查找,就形成了原型鏈2021-10-10
JS前端中的設(shè)計(jì)模式和使用場(chǎng)景示例詳解
這篇文章主要為大家介紹了JS前端中的設(shè)計(jì)模式和使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
前端使用xlsx庫(kù)導(dǎo)出帶有樣式的excel文件
這篇文章主要為大家介紹了前端使用xlsx庫(kù)導(dǎo)出帶有樣式的excel文件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
微信小程序 按鈕滑動(dòng)的實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序 按鈕滑動(dòng)的實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文能幫助到大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09
微信小程序 swiper組件構(gòu)建輪播圖的實(shí)例
這篇文章主要介紹了微信小程序 swiper組件構(gòu)建輪播圖的實(shí)例的相關(guān)資料,如有疑問請(qǐng)留言或者到本站社區(qū)交流討論,需要的朋友可以參考下2017-09-09

