原型是什么
我們創(chuàng)建的每個函數(shù)都有一個 prototype 屬性,這個屬性是一個引用,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。
為什么要使用原型
我們知道通過構(gòu)造函數(shù),我們可以方便地創(chuàng)建對象,但同時也存在一個問題,那就是每個方法都要在每個實例中重新創(chuàng)建一遍。
<script src="https://gist.github.com/zhiwei1988/d1ac7d5cb25b1529a5df651469eeb76b.js">
</script>
使用原型對象的好處是可以讓所有實例共享它所包含的屬性和方法。
組合使用構(gòu)造函數(shù)模式和原型模式
單純地使用原型模式存在的問題:
- 所有實例在默認情況下都取得相同的屬性值;
- 原型中所有屬性都是被很多實例共享的,這一點對于包含引用類型值的屬性來說,可能就不是我們想要的了。
利用構(gòu)造函數(shù)和原型對象各自的特點,構(gòu)造函數(shù)模式用于定義實例屬性,原型模式用于定義方法和共享屬性。這樣即使所有實例共享同一個方法,減少代碼的冗余,又使每個實例有自己特有的屬性。
<script src="https://gist.github.com/zhiwei1988/6050a04a74c80501eac65f7097eb6ad9.js">
</script>
原型是動態(tài)的:任何時刻原型中添加了新的屬性或方法,集成該原型的對象都能立刻使用該屬性或方法。
重寫原型:任何情況下,都可重寫原型的屬性和方法;調(diào)用對象方法時,如果在對象中找不到,將在原型中查找它。如果在對象中找到了,對象中的屬性或方法會覆蓋原型中的屬性或方法。
建立原型鏈
ECMAScript 只支持實現(xiàn)繼承,而且其實現(xiàn)繼承主要是依靠原型鏈來實現(xiàn)的。
沒錯,建立原型鏈的目的就是為了繼承。
我們先來看下構(gòu)造函數(shù)、原型和實例的關(guān)系:
[圖片上傳失敗...(image-ab70b7-1515160669449)]
- 每個構(gòu)造函數(shù)都有一個原型對象(prototype 屬性);
- 原型對象都包含一個指向構(gòu)造函數(shù)的指針;
- 實例都包含一個指向原型對象的內(nèi)部指針;
- SubType 類型的原型對象是 SuperType 類型的實例;
注意:instance.constructor 現(xiàn)在指向的是 SuperType,這是因為現(xiàn)在 instance 實例所指向的原型對象中并沒有指向構(gòu)造函數(shù)的指針,它是 SuperType 類型的一個實例,這個實例中又存在指向原型對象的指針,這個原型對象所指向的構(gòu)造函數(shù)是 SuperType。
確定原型和實例的關(guān)系
有兩種方式可確認原型和實例的關(guān)系:
alert(instance instanceof SubType);
alert(SubType.prototype.isPrototypeOf(instance));
原型鏈的問題
包含屬性值的原型對象,其屬性值會被所有實例共享,而這也正是為什么要在構(gòu)造函數(shù)中,而不是在原型對象中定義屬性的原因。在通過原型來實現(xiàn)繼承時,原型實際上會變成另一個類型的實例。于是,原先的實例屬性也就順理成章地變成了現(xiàn)在的原型屬性了。下列代碼可以用來說明這個問題:
<script src="https://gist.github.com/zhiwei1988/b4b5d685c0a17479083f9b615db02c38.js">
</script>
組合繼承
組合繼承使用原型鏈實現(xiàn)對原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承。這樣,既通過在原型上定義方法實現(xiàn)了函數(shù)復(fù)用,又能夠保證每個實例都有它自己的屬性。下面來看一個例子。
<script src="https://gist.github.com/zhiwei1988/597291e4ebe0fd6c63497f9446d28929.js">
</script>
組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,并結(jié)合了它們的優(yōu)點,是 JavaScript 中最常用的繼承模式。而且,instanceof 和 isPrototypeOf() 也能夠用于識別基于組合繼承創(chuàng)建的對象。