本人博客:【www.xiabingbao.com】javascript
在本文中,咱們講解prototype的內容主要由:什麼是prototype,prototype與函數之間的關係,prototype與實例對象之間的關係,使用proto實現一個簡單的繼承。java
在javascript中,建立的每一個函數天生都自帶着一個prototype屬性。這裏咱們要強調的是:這個prototype屬性是一個指針,指向一個對象,在這裏,咱們稱指向的這個看不到但確實存在的對象爲原型對象。其實能夠用下面一個簡單的例子來講明:微信
var proto = { name : 'wenzi', age : 25 } function Person(){ } Person.prototype = proto;
這樣Person.protptype就指向到了proto,若是還有其餘的引用(A)也指向到了proto,那麼這個引用和Person.prototype就是相等的:A==Person.prototype
; 函數
prototype指向的這個對象是真實存在的,可能不少的同窗對prototype屬性和原型對象有些混淆,咱們在這裏把原型對象叫作大SB
。大SB
與普通對象的一個不一樣之處就是,他也有一個天生的屬性:constructor,這個constructor從新指回到了函數。不過,既然大SB
也是一個對象,那麼它也是繼承於Object的,擁有Object全部的方法和屬性,好比toString()
等。性能
大SB = { constructor : Person, say : function(){ return "hello world"; } }
那麼如今咱們就能獲得兩個引用關係:this
大SB = Person.prototype; // 原型對象 = 函數.prototype;
Person = 大SB.constructor(Person.prototype.constructor); // 函數 = 原型對象.constructor(函數.prototype.constructor)spa
從運行的代碼中,能夠驗證咱們的觀點,Person.prototype的類型是object
,Person.prototype.constructor從新指回到了Person。
其實在實例對象中,也存在着一個屬性指向到大SB
中:prototype
var John = new Person(); 大SB = John.__proto__;
即 John.__proto__ 和 Person.prototype 指向的是同一個引用:John.__proto__==Person.prototype
。指針
在構造函數能設置屬性和方法,在prototype中也能設置屬性和方法,那new出的對象使用的是哪一個呢?咱們來看一個例子:code
function Person(){ this.name = "wenzi"; } Person.prototype.name = "bing"; Person.prototype.say = function(){ return "hello world"; } var John = new Person(); alert(John.name); // "wenzi" alert(John.say()); // "hello world"
從運行的結果咱們能夠看到,John.name輸出的是構造函數中的屬性值"wenzi",John.say()輸出的是"hello world"。這是由於,當讀取某個對象的屬性值時,會首先在實例對象中進行搜索,若搜索到則直接進行返回;若搜索不到則去原型對象中進行尋找。所以在使用John調用say()方法時是正確的,不會報錯。並且是進行了兩次的搜索。
雖然say()
是掛載在prototype上,John也一樣能直接進行訪問。可是,咱們不能直接對John.say進行修改從而影響prototype的say()
方法,以下:
John.say = function(){ return "this has changed"; } John.say(); // "this has changed" var Tom = new Person(); Tom.say(); // "hello world"
從運行John.say()
的結果來看,say()方法確實發生了變化。可是,當咱們再new出一個新對象Tom時,Tom.say()
返回的仍是"hello world",這是爲何呢?
由於,對John.say()
進行修改時,不是修改了prototype上的say()
,而是給John這個實例對象添加了一個say()
方法,從而屏蔽了prototype上的say()
方法,由上面的尋找順序咱們能夠知道,若實例對象存在這個方法就直接返回了,再也不去原型對象上尋找。而new出的新對象Tom
在調用say()方法時,Tom本身是沒有say()方法的,只能去原型對象上尋找,所以返回的仍是"hello world"。因此,對John.say進行修改時,不是修改了prototype上的say()方法,而是給John添加了實例方法,屏蔽掉了prototype上的方法而已。那如何才能修改prototype上的方法呢,好辦,直接在prototype上修改:
Person.prototype.say = function(){ return "wenzi's blog"; }
這樣就能修改prototype的say()方法了。
在上面的例子中,咱們都是大部分給prototype添加屬性或方法,原本prototype指向的是大SB
,若咱們給prototype添加屬性或方法時,就是給大SB
添加屬性或方法:
Person.prototype.name = "wenzi";
而大SB
裏其餘的屬性和方法是不受影響的,constructor依然指回到Person。可是,若咱們這樣寫:
Person.prototype = { name : "wenzi", say : function(){ return "my name is name" } }
就是對Person的prototype重寫了,讓prototype進行了從新的指向,原本Person.prototype指向的是大SB
,但是如今卻指向了CTM
,而CTM
裏也沒有constructor屬性指回到Person,如果想從新指回到Person,還得手動添加一個constructor屬性:
Person.prototype = { constructor : Person, // 從新指回到Person name : "wenzi", say : function(){ return "my name is name" } }
這樣就能手動構造出新的原型對象了,new出的實例對象也能使用這個prototype上的屬性和方法。
可是這裏還存在一個問題,若以前已經有new出的實例對象,而後再修改Person.prototype,以前的實例對象是沒法使用新的原型對象(CTM
)上的屬性和方法的,由於以前的實例對象的__proto__
指向的依然是大SB
。所以,請慎重重寫prototype
說到prototype就不得說prototype繼承,咱們經過給prototype上添加屬性和方法,就能使該構造函數全部的實例對象擁有屬性和方法。咱們先來看下面的代碼:
function Father(){ this.name = "father"; this.age = 43; } Father.prototype.job = "Doctor"; Father.prototype.getName = function(){ return this.name; } function Son(){ this.name = "son"; } Son.prototype = new Father(); // Son的prototype指向Father的實例對象 var John = new Son(); for(var k in John){ console.log(k+' : '+john[k]); } /* 輸出結果: name : son age : 43 job : Doctor getName : function (){ return this.name; } */
從上面的例子中能夠看到,Son的實例對象繼承了Father中全部的屬性和方法。固然,若Son的對象實例中存在的,還依然保留。不過Son原型中的屬性和方法是會被完全覆蓋的。咱們來分析一下這是爲何?
Son的prototype指向的是Father的一個實例,咱們把這拆成兩步:
var father = new Father(); Son.prototype = father;
在實例father
中既有name,age屬性,也有Father.prototype中的屬性和方法,咱們對father循環看一下:
for(var k in father){ console.log(k, father[k]); } /* name father age 43 job Doctor getName Father.getName() */
因爲constructor是不可枚舉的類型,在for~in循環裏是輸出不了constructor的,其實father是這樣的:
father = { constructor : Father, name : father age : 43 job : Doctor getName : Father.getName() }
所以Son的prototype指向的就是上面father的內容。到此,Son的prototype中的constructor的指向也發生了改變,原本Son.prototype是指向到Son的,如今指向到了Father。即徹底重寫了Son的prototype,從新指向了另外一個引用。因此,咱們就能知道爲何對象實例中的屬性可以保存,而prototype中的會完全被刪除了。
若是想給Son添加原型方法或屬性,那隻能是在Son.prototype = new Father();
以後進行添加:
Son.prototype = new Father(); Son.prototype.getJob = function(){ return this.job; }
這樣就能添加上getJob方法了。
到這裏,會有人問,若是Son.prototype = Father.prototype
會怎麼樣呢?咱們剛纔也說了Father.prototype指向的是大SB
,而大SB
也是一個實例對象,是Object的一個實例,這就至關於:
Son.prototype == 大SB; // 指向了大SB
所以,若是是Son.prototype = Father.prototype
的話,那麼Son只能繼承到Father.prototype上的job
和getName()
,其餘的屬性是獲取不到的。
本文簡單的介紹了一下prototype,讓咱們能對prototype有個淺顯的認識。固然,博主才疏學淺,文章裏會有不少疏漏和不完善的地方,歡迎你們批評指正。
本人博客:【www.xiabingbao.com】
歡迎你們關注個人微信公衆號:wenzichel