我曾嘗試理解關(guān)于prototype的相關(guān)概念乡革,最初理解起來晦澀難懂牵啦,加上當時用的地方又少亚情。后面漸漸明白,當你需要了解一個東西的時候哈雏,刻意的去理解是沒有本質(zhì)的作用的楞件,但是能在你的腦海里留下一絲印象,當你真正遇到的時候裳瘪,會想起曾經(jīng)看到過土浸,時機成熟的時候再去理解,會有不少收獲盹愚,輪番看個幾遍栅迄,拿上實例解析,會發(fā)現(xiàn)豁然開朗皆怕。
本文闡述的相關(guān)內(nèi)容:
- 創(chuàng)建對象的幾種模式以及創(chuàng)建的過程
- 原型鏈prototype的理解毅舆,以及
prototype
與__proto__
([[Prototype]]
)的關(guān)系 - 繼承的幾種實現(xiàn)
1.常見模式與原型鏈的理解
a.構(gòu)造函數(shù)創(chuàng)建
function Test() {
//
}
流程
- 創(chuàng)建函數(shù)的時候會默認為Test創(chuàng)建一個prototype屬性西篓,
Test.prototype
包含一個指針指向的是Object.prototype
- prototype默認會有一個constructor,且
Test.prototype.constructor = Test
- prototype里的其它方法都是從Object繼承而來
// 調(diào)用構(gòu)造函數(shù)創(chuàng)建實例
var instance = new Test()
此處的instance包含了一個指針指向構(gòu)造函數(shù)的原型憋活,(此處的指針在chrome里叫__proto__
岂津,也等于[[Prototype]]
)
b.原型模式
由上我們可以知道,默認創(chuàng)建的prototype屬性只擁有constructor和繼承至Object的屬性悦即,原型模式就是為prototype添加屬性和方法
Test.prototype.getName = ()=> {
alert('name')
}
此時的instance實例就擁有了getName方法吮成,因為實例的指針是指向Test.prototype的
instance.__proto__ === Test.prototype
如下圖所示
![897RVF]E5@IX$)`IVJ3BOSY.png](http://upload-images.jianshu.io/upload_images/3637499-2c25e10269d8bbbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這里我們可得知:實例instance與構(gòu)造函數(shù)之間是通過原型prototype
來相關(guān)聯(lián)的。
c.組合模式
這種模式我們用的最多辜梳,其實也是原型模式的另一種寫法粱甫,只不過有一點小區(qū)別而已
function Test() {}
Test.prototype = {
getName() {
alert('name')
}
}
我們經(jīng)常會這么直接重寫prototype方法,由上我們可知作瞄,prototype會默認自帶constructor屬性指向構(gòu)造函數(shù)本身茶宵,那么重寫以后呢?
Test.prototype.constructor === Object
// 而并不等于Test了
// 因為重寫以后相當于利用字面量方式創(chuàng)建一個實例對象宗挥,這個實例的構(gòu)造函數(shù)是指向Object本身的
當然我們也可以手動賦值constructor
Test.prototype = {
constructor: Test,
getName() {
alert('name')
}
}
那么又會有疑問了constructor
要不要有何意義乌庶?我覺得constructor意義僅僅是為了來鑒別原型所屬的構(gòu)造函數(shù)吧。
當需要獲取某個屬性的時候契耿,會先從實例中查找瞒大,沒有就根據(jù)指針所指向的原型去查找,依次向上搪桂,直到實例的指針__proto__
指向為null時停止查找透敌,例如:
// 1 讀取name
instance.name
// 2 instance.__proto__ === Test.prototype
Test.prototype.name
// 3 Test.prototype.__proto__ === Object.prototype
Object.prototype.name
// 4
Object.prototype.__proto__ === null
當找到了這個屬性就會直接返回,而不會繼續(xù)查找锅棕,即使這個屬性值為null拙泽,想要繼續(xù)查找,我們可以通過delete
操作符來實現(xiàn)裸燎。
由這里我們自然可以想到Array, Date, Function, String
顾瞻,都是一個構(gòu)造函數(shù),他們的原型的指針都是指向Object.prototype
德绿,它們就像我這里定義的Test
一樣荷荤,只不過是原生自帶而已
d.幾個有用的方法
-
Object.getPrototypeOf()
獲取某個實例的指針所指向的原型
Object.getPrototypeOf(instance) === Test.prototype
-
hasOwnProperty
判斷一個屬性是存在于實例中還是存在于原型中,如圖所示:
NY~N}CNR`}8W%4QA$M8LFE4.png -
in
操作符移稳,無論該屬性是否可枚舉
'name' in instance // true
'getName' in instance // true
無論屬性是在實例中蕴纳,還是在原型中都返回true,所以當我們需要判斷一個屬性存在與實例中个粱,還是原型中有2種辦法
// 一種就是使用hasOwnProperty判斷在實例中
// 另一種判斷在原型中
instance.hasOwnProperty('getName') === false && 'getName' in instance === true
-
for ... in
操作符也是一樣的古毛,但只會列出可枚舉的屬性,ie8版本的bug是無論該屬性是否可枚舉,都會列出
D(%S__GN8404{H9X6PW$DVK.png
name是在實例中定義的稻薇,getName是在原型中定義的 Object.keys()
則不一樣嫂冻,它返回一個對象上所有可枚舉的屬性,僅僅是該實例中的
Object.keys(instance)
// ["name"]
e.總結(jié)
以上討論了構(gòu)造函數(shù)塞椎,原型和實例的關(guān)系:
- 每個構(gòu)造函數(shù)都有原型對象
- 每個原型對象都有一個
constructor
指針指向構(gòu)造函數(shù) - 每個實例都有一個
__proto__
指針指向原型
2.繼承
繼承的實質(zhì)是利用構(gòu)造函數(shù)的原型 = 某個構(gòu)造函數(shù)的實例桨仿,以此來形成原型鏈。例如
// 定義父類
function Parent() {}
Parent.prototype.getName = ()=> {
console.log('parent')
}
// 實例化父類
let parent = new Parent()
// 定義子類
function Child() {}
Child.prototype = parent
// 實例化子類
let child = new Child()
child.getName() // parent
// 此時
child.constructor === parent.constructor === Parent
a.最經(jīng)典的繼承模式
function Parent(name) {
this.name = name
this.colors = ['red']
}
Parent.prototype.getName = function() {
console.log(this.name)
}
// 實例化父類
let parent = new Parent()
function Child(age, name) {
Parent.call(this, name)
this.age = age
}
Child.prototype = parent
// 實例化子類
let child = new Child(1, 'aaa')
child.getName() // parent
這里會讓我想到ES6中的class繼承
class Parent {
constructor(name) {
this.name = name
this.colors = ['red']
}
getName() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(age, name) {
super(name)
}
}
let child = new Child(1, 'aaa')
child.getName() // parent
其實是一個道理案狠,這里我們不難想到服傍,將Child.prototype
指向parent實例,就是利用原型實現(xiàn)的繼承骂铁,而為了每個實例都擁有各自的colors和name
吹零,也就是基礎(chǔ)屬性,在Child的構(gòu)造函數(shù)中call
調(diào)用了Parent的構(gòu)造函數(shù)从铲,相當于每次實例化的時候都初始化一遍colors和name
瘪校,而不是所有實例共享原型鏈中的colors和name
以上也是自己一邊學(xué)習一邊整理的,邏輯有點混亂名段,見諒,還望有誤之處指出泣懊,不勝感激伸辟!
參考:
紅寶書第六章
MDN 繼承與原型鏈
理解JavaScript的原型鏈和繼承
相關(guān)