全面解析js中的原型,原型對象,原型鏈
理解原型
我們創(chuàng)建的每一個函數(shù)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法??慈缦吕樱?/p>
function Person(){ } Person.prototype.name = 'ccc' Person.prototype.age = 18 Person.prototype.sayName = function (){ console.log(this.name); } var person1 = new Person() person1.sayName() // --> ccc var person2 = new Person() person2.sayName() // --> ccc console.log(person1.sayName === person2.sayName) // --> true
理解原型對象
根據(jù)上面代碼,看下圖:
需要理解三點(diǎn):
- 我們只要創(chuàng)建了一個新的函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性,指向函數(shù)的原型對象。即Person(構(gòu)造函數(shù))有一個prototype指針,指向Person.prototype
- 默認(rèn)情況下,每個原型對象上都會創(chuàng)建一個constructor(構(gòu)造函數(shù))屬性,這個屬性是一個指向prototype屬性所在函數(shù)的指針
- 每個實(shí)例的內(nèi)部都有一個指針(內(nèi)部屬性) ,指向構(gòu)造函數(shù)的原型對象。即 person1 和person2 身上都有一個內(nèi)部屬性__proto__(在ECMAscript中管這個指針叫[[prototype]],雖然在腳本中沒有標(biāo)準(zhǔn)的方式訪問[[prototype]],但是firefox,ie,chrome都支持一個屬性叫__proto__) 指向Person.prototype
注意:person1 和person2 實(shí)例與構(gòu)造函數(shù)之間沒有直接的關(guān)系。
在之前我們提到,所有實(shí)現(xiàn)中無法訪問到[[prototype]],那我們?nèi)绾沃缹?shí)例和原型對象之間是否存在關(guān)系呢?這里可以通過兩個方法來判斷:
- 原型對線上的方法:isPrototypeOf(),如:
console.log(Person.prototype.isPrototypeOf(person1)) // --> true
- ECMAscript5中新增的一個方法:Object.getPrototypeOf(),這個方法返回[[prototype]]的值。如:console.log
(Object.getPrototypeOf(person1) === Person.prototype) // --> true
實(shí)例屬性與原型屬性的關(guān)系
前面我們提到過,原型最初只包含constructor屬性,而該屬性也是共享的,因此可以通過對象實(shí)例訪問。雖然可以通過對象實(shí)例訪問保存在原型中的值,但卻不能通過對象實(shí)例重寫原型中的值。如果我們在實(shí)例中添加了一個屬性,而改屬性與實(shí)例原型中的一個屬性同名,那就會在實(shí)例上創(chuàng)建該屬性并屏蔽原型中的那個屬性。如下:
function Person() {} Person.prototype.name = "ccc"; Person.prototype.age = 18; Person.prototype.sayName = function() { console.log(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = 'www' // 在person1中添加一個name屬性 person1.sayName() // --> 'www'————'來自實(shí)例' person2.sayName() // --> 'ccc'————'來自原型' console.log(person1.hasOwnProperty('name')) // --> true console.log(person2.hasOwnProperty('name')) // --> false delete person1.name // --> 刪除person1中新添加的name屬性 person1.sayName() // -->'ccc'————'來自原型'
我們?nèi)绾闻袛嘁粋€屬性,到底是實(shí)例上的屬性還是原型上的屬性?這里可以通過hasOwnProperty()方法來檢測一個屬性是存在于實(shí)例中還是存在于原型中。(此方法繼承于Object)
下圖詳細(xì)分析了上面例子在不同情況下的實(shí)現(xiàn)與原型的關(guān)系:(省略了Person構(gòu)造函數(shù)的的關(guān)系)
更簡單的原型語法
我們不可能總像之前的例子一樣,沒添加一個屬性和方法就要敲一遍,Person.prototype。為了減少不必要的輸入,更常見的方法是像下面這樣:
function Person(){} Person.prototype ={ name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
在上面代碼中,我們將Person.prototype設(shè)置為等于一個以對象字面量形式創(chuàng)建的新對象。最終結(jié)果相同,但有一個例外,constructor屬性不再指向Person了。前面我們介紹過,每創(chuàng)建一個函數(shù),就會同時創(chuàng)建它的prototype對象,這個對象也會自動獲得constructor屬性。但是在我們使用的新語法中,本質(zhì)上完全重寫了默認(rèn)的prototype對象,因此constructor屬性也就變成了新對象的constructor屬性(指向Object構(gòu)造函數(shù)),不再指向Person函數(shù)了。此時,盡管instanceof操作符還能返回正確的結(jié)果,但通過constructor已經(jīng)無法確定對象的類型了。如下:
var person1 = new Person() console.log(person1 instanceof Object) // --> true console.log(person1 instanceof Person) // --> true console.log(person1.constructor === Person) // --> false console.log(person1.constructor === Object) // --> true
這里用instanceof操作符測試Object和Person仍然返回true,constructor屬性則等于Object,不等于Person了,如果constructor真的很重要可以像下面這樣寫:
function Person(){} Person.prototype ={ constructor: Person, // --> 重設(shè) name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
但是這會引起一個新問題,用上述方式重置constructor屬性會導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true。而默認(rèn)情況下,原生的constructor屬性是不可枚舉的。因此如果你要使用兼容ECMAscript5的JavaScript引擎,可以試一試Object.defineProperty()。
function Person(){} Person.constructor = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } // 重設(shè)構(gòu)造函數(shù),只適用于ECMAscript5兼容的瀏覽器 Object.defineProperty(Person.constructor, "constructor", { enumerable: false, value: Person })
原型的動態(tài)性
由于原型中查找值的過程是一次搜索,因此我們對原型對象所做的任何修改都能立即從實(shí)例上反映出來。比如:
function Person(){} var person1 = new Person() Person.prototype.sayHi= function(){ console.log('hi') } person1.sayHi()
上述代碼我們先創(chuàng)建了一個Person實(shí)例,并將其保存在person1中,然后在Person.prototype中添加了sayHi()方法。即使person1是添加新方法之前創(chuàng)建的,但它仍然可以訪問這個方法。原因是實(shí)例與原型之間的松散的連接關(guān)系。
盡管可以隨時為原型添加屬性和方法,并立即能夠在實(shí)例中反映出來。但是如果重寫整個原型對象,那么情況就不一樣了。看如下代碼:
function Person(){} var person1 = new Person() Person.prototype = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } person1.sayName() // --> error
看下圖分析:
調(diào)用構(gòu)造函數(shù)時為實(shí)例添加了一個指向最初原型的[[prototype]]指針,而把原型修改為另外一個對象就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系。請記?。簩?shí)例中的指針僅指向原型,而不指向構(gòu)造函數(shù)。
理解原型鏈
原型鏈?zhǔn)菍?shí)現(xiàn)繼承的主要方法。其基本思想是讓一個引用類型繼承另一個引用類型的屬性和方法。在理解原型鏈之前,我們首先得捋一下,原型,原型對象,實(shí)例之間的關(guān)系:每一個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個指向原型對象的內(nèi)部指針。假如我們讓原型對象等于另一個類型的實(shí)例會怎么樣?顯然,這個原型對象將會包含一個指向另一個原型的指針。先看代碼在看圖:
function SuperType(){ this.property = true } SuperType.prototype.getSuperValue = function(){ return this.property } function SubType(){ this.subProperty = false } // 繼承了SuperType SubType.prototype = new SuperType() SubType.prototype.getSubValue = function (){ return this.subProperty } var instance = new SubType() console.log(instance.getSuperValue()) // --> true
上述代碼定義了兩個類型:SuperType和SubType。每個類型分別有一個屬性和一個方法。
分析上圖:instance 指向SubType原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然還在SuperType.prototype中,但property則位于SubType.prototype中。這是因?yàn)閜roperty是一個實(shí)例屬性,而getSuperValue()則是一個原型方法。既然SubType.prototype現(xiàn)在是SuperType的實(shí)例,那么property當(dāng)然就位于該實(shí)例中。此外要注意,instance.constructor現(xiàn)在指向的是SuperType,這是因?yàn)樵瓉淼腟ubType.prototype中的constructor被重寫了的緣故。
為什么會返回true?
分析:調(diào)用instance.getSuperValue()方法會經(jīng)歷三個搜索步驟:
搜索實(shí)例
搜索SubType.prototype
搜索SuperType.prototype,直到這里才找到方法。在找不到屬性或方法的情況下,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈末端才會停下來。
別忘記默認(rèn)的原型
要知道,所有的引用類型默認(rèn)都繼承了Object,而這個繼承也是通過原型鏈實(shí)現(xiàn)的。所有函數(shù)的默認(rèn)原型都是Object的實(shí)例,因此默認(rèn)原型都會包含一個內(nèi)部指針,指向Object.prototype,這也正是所有自定義類型都會有toString(),valueOf()方法的原因。所以完整的原型鏈應(yīng)該如下:
看下圖,subType的內(nèi)部:
詳細(xì)圖解:
總之一句話,SubType繼承了SuperType,而SuperType繼承了Object。當(dāng)調(diào)用instanct.toString()的時候,實(shí)際上調(diào)用的是保存在Object.prototype中的那個方法。
確定原型和實(shí)例的關(guān)系
當(dāng)一個原型鏈很長的時候,想要確定原型和實(shí)例的關(guān)系,總共有兩種方法:
使用instanceof 操作符,只要用這個操作符來測試實(shí)例與原型鏈中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會返回true。
console.log(instance instanceof Object) // --> true console.log(instance instanceof SuperType) // --> true console.log(instance instanceof SubType) // --> true
使用isPrototypeOf()方法,跟instanctof判別方法類似,只要原型鏈中出現(xiàn)過的原型,都會返回true。
console.log(Object.prototype.isPrototypeOf(instance)) // --> true console.log(SuperType.prototype.isPrototypeOf(instance)) // --> true console.log(SubType.prototype.isPrototypeOf(instance)) // --> true
謹(jǐn)慎地定義方法
子類型有時候需要覆蓋超類型中的某個方法,或者需要添加超類型中不存在的某個方法。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后。如下:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property } function SubType(){ this.subProperty = false; } // 繼承了 SuperType SubType.prototype = new SuperType() // 添加新方法 SubType.prototype.getSubValue = function(){ return this.subProperty } // 重寫超類型中的方法 SubType.prototype.getSuperValue = function(){ return false } var instance = new SubType() console.log(instance.getSuperValue()) // --> false var instanceSuper = new SuperType() console.log(instanceSuper.getSuperValue()) // -> true
上述代碼中,第一個方法getSubValue()被添加到了SubType中。第二個方法getSuperValue()是原型鏈中已經(jīng)存在的一個方法,但重寫這個方法將會屏蔽原來的那個方法。即當(dāng)通過SubType的實(shí)例調(diào)用getSuperValue()時,調(diào)用的就是這個重新定義的方法,但通過SuperType的實(shí)例調(diào)用getSuperValue()時,還會繼續(xù)調(diào)用原來的那個方法。還有一點(diǎn),在通過原型鏈實(shí)現(xiàn)繼承的時候,不能使用對象自變量創(chuàng)建原型方法,因?yàn)檫@樣會重寫原型鏈,導(dǎo)致原型鏈被切斷。
原型鏈的問題
通過原型來實(shí)現(xiàn)繼承時,原型實(shí)際上會變成另一個類型的實(shí)例,于是,原先的實(shí)例屬性就變成了現(xiàn)在的原型屬性了,這就會導(dǎo)致屬性被共享??慈缦麓a:
function SuperType(){ this.colors = ['white', 'blue'] } function SubType(){ } // 繼承了SuperType SubType.prototype = new SuperType() var instance1 = new SubType() instance1.colors.push('red') var instance2 = new SubType() console.log(instance1.colors) // -->["white", "blue", "red"] console.log(instance2.colors) // -->["white", "blue", "red"]
在創(chuàng)建子類型的實(shí)例時,不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)。實(shí)際上,應(yīng)該是沒有辦法在不影響所有對象實(shí)例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)。因此,在實(shí)踐中很少會單獨(dú)使用原型鏈。
以上就是圖解js中的原型,原型對象,原型鏈的詳細(xì)內(nèi)容,更多關(guān)于js中的原型,原型對象,原型鏈的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
CKEditor擴(kuò)展插件:自動排版功能autoformat插件實(shí)現(xiàn)方法詳解
這篇文章主要介紹了CKEditor擴(kuò)展插件:自動排版功能autoformat插件實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了CKEditor擴(kuò)展插件實(shí)現(xiàn)自動排版功能的autoformat插件具體定義、配置與使用技巧,需要的朋友可以參考下2020-02-02JS中的THIS和WINDOW.EVENT.SRCELEMENT詳解
對于js初學(xué)著必須理解this和srcElement的應(yīng)用,這也是面試中經(jīng)常考到的。下面我們就通過幾個示例來詳細(xì)了解下2015-05-05javascript實(shí)現(xiàn)給定半徑求出圓的面積
這篇文章主要介紹了javascript實(shí)現(xiàn)給定半徑求出圓的面積的相關(guān)資料,需要的朋友可以參考下2015-06-06javascript 四則運(yùn)算精度修正函數(shù)代碼
JS預(yù)算精度問題確實(shí)很麻煩,這個能解決一些問題,雖然有bug.2010-05-05