咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。看以下例子:chrome
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
根據上面代碼,看下圖:
瀏覽器
須要理解三點:函數
注意:person1 和person2 實例與構造函數之間沒有直接的關係。測試
在以前咱們提到,全部實現中沒法訪問到[[prototype]],那咱們如何知道實例和原型對象之間是否存在關係呢?這裏能夠經過兩個方法來判斷:this
console.log(Person.prototype.isPrototypeOf(person1)) // --> true
console.log(Object.getPrototypeOf(person1) === Person.prototype) // --> true
前面咱們提到過,原型最初只包含constructor屬性,而該屬性也是共享的,所以能夠經過對象實例訪問。雖然能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值。若是咱們在實例中添加了一個屬性,而改屬性與實例原型中的一個屬性同名,那就會在實例上建立該屬性並屏蔽原型中的那個屬性。以下:firefox
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'————'來自實例' person2.sayName() // --> 'ccc'————'來自原型' console.log(person1.hasOwnProperty('name')) // --> true console.log(person2.hasOwnProperty('name')) // --> false delete person1.name // --> 刪除person1中新添加的name屬性 person1.sayName() // -->'ccc'————'來自原型'
咱們如何判斷一個屬性,究竟是實例上的屬性仍是原型上的屬性?這裏能夠經過hasOwnProperty()方法來檢測一個屬性是存在於實例中仍是存在於原型中。(此方法繼承於Object)prototype
下圖詳細分析了上面例子在不一樣狀況下的實現與原型的關係:(省略了Person構造函數的的關係)
指針
咱們不可能總像以前的例子同樣,沒添加一個屬性和方法就要敲一遍,Person.prototype。爲了減小沒必要要的輸入,更常見的方法是像下面這樣:code
function Person(){} Person.prototype ={ name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
在上面代碼中,咱們將Person.prototype設置爲等於一個以對象字面量形式建立的新對象。最終結果相同,但有一個例外,constructor屬性再也不指向Person了。前面咱們介紹過,每建立一個函數,就會同時建立它的prototype對象,這個對象也會自動得到constructor屬性。可是在咱們使用的新語法中,本質上徹底重寫了默認的prototype對象,所以constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數),再也不指向Person函數了。此時,儘管instanceof操做符還能返回正確的結果,但經過constructor已經沒法肯定對象的類型了。以下:對象
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, // --> 重設 name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
可是這會引發一個新問題,用上述方式重置constructor屬性會致使它的[[Enumerable]]特性被設置爲true。而默認狀況下,原生的constructor屬性是不可枚舉的。所以若是你要使用兼容ECMAscript5的JavaScript引擎,能夠試一試Object.defineProperty()。
function Person(){} Person.constructor = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } // 重設構造函數,只適用於ECMAscript5兼容的瀏覽器 Object.defineProperty(Person.constructor, "constructor", { enumerable: false, value: Person })
因爲原型中查找值的過程是一次搜索,所以咱們對原型對象所作的任何修改都能當即從實例上反映出來。好比:
function Person(){} var person1 = new Person() Person.prototype.sayHi= function(){ console.log('hi') } person1.sayHi()
上述代碼咱們先建立了一個Person實例,並將其保存在person1中,而後在Person.prototype中添加了sayHi()方法。即便person1是添加新方法以前建立的,但它仍然能夠訪問這個方法。緣由是實例與原型之間的鬆散的鏈接關係。
儘管能夠隨時爲原型添加屬性和方法,並當即可以在實例中反映出來。可是若是重寫整個原型對象,那麼狀況就不同了。看以下代碼:
function Person(){} var person1 = new Person() Person.prototype = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } person1.sayName() // --> error
看下圖分析:
調用構造函數時爲實例添加了一個指向最初原型的[[prototype]]指針,而把原型修改成另一個對線更久等於切斷了構造函數與最初原型之間的聯繫。請記住:實例中的指針僅指向原型,而不指向構造函數。
原型鏈的內容明天再寫,今天有點晚了。