一文帶你搞懂JavaScript中的原型和原型鏈
原型和原型鏈
JavaScript和Java這種面向?qū)ο蟮恼Z言不太一樣,JavaScript是基于原型繼承的語言。雖然在ES6及之后,class、extend語法也漸漸取代了之前修改prototype實現(xiàn)繼承的方式,但本質(zhì)上還是通過修改prototype來實現(xiàn)繼承的。本文則是重點對prototype相關(guān)知識點做拆解和梳理
通過class聲明并實例化對象
在java中聲明并實例化對象是這樣的
package geek.springboot.application.entity;
public class WechatUser {
private String name;
private String openId;
private String avatar;
public WechatUser(String name, String openId, String avatar) {
this.name = name;
this.openId = openId;
this.avatar = avatar;
}
// 打印輸出當(dāng)前微信用戶信息
public void print() {
System.out.println("name: " + this.name + " openId: " + this.openId + " avatar: " + this.avatar);
}
// java程序啟動入口
public static void main(String[] args) {
WechatUser user = new WechatUser("Bruse", "opwrogfajadfoa113", "avatar-1.png");
user.print();
}
}JavaScript實例化對象是這樣的
class WechatUser {
constructor(name, openId, avatar) {
this.name = name
this.openId = openId
this.avatar = avatar
}
print(){
console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar}`)
}
}
const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg')
user.print()輸出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg
從語法上看兩者差別并不大,class定義對象模板,定義了對象該有的屬性和方法,然后通過new關(guān)鍵字將對象進行實例化
extend繼承
通過extend便可讓不同的class實現(xiàn)繼承關(guān)系,達到代碼復(fù)用的效果
class MiniProgramUser extends WechatUser {
constructor(name, openId, avatar, appId) {
// 調(diào)用父類的構(gòu)造函數(shù)
super(name, openId, avatar);
this.appId = appId
}
// 重寫父類方法
print() {
console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar} appId: ${this.appId}`)
}
}輸出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg appId: appId13322
原型
以上示例演示了如何用class進行聲明和實例化對象,但其實class只不過是所謂的語法糖,本質(zhì)上JavaScript并不會像Java那樣基于類實現(xiàn)面向?qū)ο蟆?/p>
class WechatUser{}實際上也還是個函數(shù),class只是個語法糖,它等同于
function WechatUser() {}隱式原型和顯式原型
隱式原型
const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg')
console.log('user.__proto__ ', user.__proto__)以上邊的代碼為例,其實在創(chuàng)建出來的user對象中,有一個__protocol__屬性,這個即每個實例都有的隱式原型,打印輸出如下

顯式原型
console.log('WechatUser.prototype ',WechatUser.prototype)輸出WechatUser的prototype屬性,prototype即原型的意思,每個class(function)都有顯式原型,結(jié)果如下

隱式原型和顯式原型的關(guān)系
可以看到無論是user.__proto__還是WechatUser.prototype,都有print方法,constructor都是WechatUser,那么是否也就意味著user.__proto__[實例的隱式原型]===WechatUser.prototype[class的顯式原型] ?
console.log('equals ', user.__proto__ === WechatUser.prototype)輸出為equals true,證明user.__proto__的確等于WechatUser.prototype,引用地址是同一個。
這里的關(guān)系可以用下圖表示

- 每個class都有顯式原型prototype
- 每個實例都有隱式原型__proto__
- 實例的__proto__指向其所對應(yīng)的class的prototype
基于原型的屬性/方法查找
基于上邊的內(nèi)容,其實可以總結(jié)出:獲取實例屬性或執(zhí)行方法時,會先在實例自身進行尋找有沒有相關(guān)的屬性或方法,有的話就獲取或調(diào)用,沒有的話,會順著實例的__proto__往上找到實例對應(yīng)的class的prototype,并對prototype進行變量查找或方法調(diào)用。這也就是所謂的基于原型的繼承方式
原型鏈
搞明白了基于原型是怎么回事后,那接下來就是多個原型之間的關(guān)聯(lián)形成原型鏈
const miniUser = new MiniProgramUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg', "appId13322")這里聲明一個miniUser,它是基于MiniProgramUser實例化的,所以miniUser.__proto__相等于MiniProgramUser.prototype。
但其實MiniProgramUser.prototype也有一個__proto__屬性,輸出如下

miniUser的隱式原型等于MiniProgramUser的顯式原型,可MiniProgramUser的顯式原型的隱式原型(是有點繞)又等于誰呢?
因為定義MiniProgramUser這個class的時候,使用了extend關(guān)鍵詞,表示其繼承于WechatUser,而且WechatUser也有自己的prototype,輸出如下

那么嘗試將MiniProgramUser.prototype.__proto__與WechatUser.prototype比較,結(jié)果如下
console.log('equals', MiniProgramUser.prototype.__proto__ === WechatUser.prototype)輸出equals true,證明MiniProgramUser的顯式原型的隱式原型等于WechatUser的顯示原型,隱約間形成了一條原型鏈
原型鏈的盡頭
那么在這里其實也可以做一個舉一反三,既然每個class的prototype都會有一個__proto__,既然WechatUser這個class并沒有在代碼中顯式指定繼承于哪個class,那么WechatUser.prototype.__proto__應(yīng)該就等同于Object.prototype,輸出驗證如下
console.log(WechatUser.prototype.__proto__ === Object.prototype)
結(jié)果為true
這里也有一個知識點,因為Object在JavaScript所有對象中是最頂級的存在了,所以雖然Object的prototype也有__proto__,但它實際上不指向任何對象,僅為null
console.log('Object.prototype.__proto__ ', Object.prototype.__proto__)輸出 Object.prototype.__proto__ null
原型鏈總結(jié)
這里可以用一張圖清楚表示形成的原型鏈?zhǔn)窃趺礃拥?/p>

typeof vs instanceof
typeof
typeof是用來判斷當(dāng)前變量是何種類型?基本類型?引用類型?也就是說它能
- 識別所有值類型
- 識別函數(shù)
- 判斷是否引用類型,但只能判斷出為
object,沒法再細(xì)分
判斷值類型
const name = 'Bruse' typeof name // 輸出'string'
const sym = Symbol('sym') typeof sym // 輸出'symbol'
const done = false typeof done // 輸出'boolean'識別函數(shù)
typeof console.log // 'function'
function print () { console.log(1+1) }
typeof print // 'function'引用類型則非常籠統(tǒng)地識別為object
typeof null // 'object'
typeof [1,2,3] // 'object'
typeof {name: 'Bruse', age: 16} // 'object'instanceof
instanceof也是用作類型判斷的,只不過比typeof更精準(zhǔn)了,它可以判斷出當(dāng)前變量是否該class構(gòu)建出來的。
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Object // true
miniUser instanceof MiniProgramUser // true
miniUser instanceof WechatUser // true
miniUser instanceof Object // true結(jié)合出上邊原型鏈的知識,其實可以搞清楚instanceof的原理,其實就是根據(jù)instanceof左邊變量miniUser的原型鏈一層層往上找,判斷prototype或__proto__是否等于instanceof右邊的class WechatUser的prototype
到此這篇關(guān)于一文帶你搞懂JavaScript中的原型和原型鏈的文章就介紹到這了,更多相關(guān)JavaScript原型和原型鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實現(xiàn)頁面滾動圖片加載(仿lazyload效果)
網(wǎng)上的很多這樣的效果都是用jQuery的方法,可是如果不用jQuery的站長難道就不能用這種方法了么2011-07-07
用javascript對一個json數(shù)組深度賦值示例
本節(jié)主要介紹了用javascript對一個json數(shù)組深度賦值的具體實現(xiàn),需要的朋友可以參考下2014-07-07
JavaScript輸出當(dāng)前時間Unix時間戳的方法
這篇文章主要介紹了JavaScript輸出當(dāng)前時間Unix時間戳的方法,涉及javascript中Date及getTime等函數(shù)操作時間的使用技巧,需要的朋友可以參考下2015-04-04

