講原型的時候,我們應該先要記住以下幾個要點,這幾個要點是理解原型的關鍵:
1、所有的引用類型(數組、函數、對象)可以自由擴展屬性(除null以外)。
2、所有的引用類型都有一個’_ _ proto_ _'屬性(也叫隱式原型,它是一個普通的對象)。
3、所有的函數都有一個’prototype’屬性(這也叫顯式原型,它也是一個普通的對象)。
4、所有引用類型,它的’_ _ proto_ _'屬性指向它的構造函數的’prototype’屬性。
5、當試圖得到一個對象的屬性時,如果這個對象本身不存在這個屬性,那么就會去它的’_ _ proto_ _'屬性(也就是它的構造函數的’prototype’屬性)中去尋找。
那么要點說完了,我們就根據這些要點來理解原型和原型鏈。
原型
我們先來看一個原型的例子。
//這是一個構造函數
function Foo(name,age){
this.name=name;
this.age=age;
}
/*根據要點3,所有的函數都有一個prototype屬性,這個屬性是一個對象
再根據要點1,所有的對象可以自由擴展屬性
於是就有了以下寫法*/
Foo.prototype={
// prototype對象里面又有其他的屬性
showName:function(){
console.log("I'm "+this.name);//this是什么要看執行的時候誰調用了這個函數
},
showAge:function(){
console.log("And I'm "+this.age);//this是什么要看執行的時候誰調用了這個函數
}
}
var fn=new Foo('小明',19)
/*當試圖得到一個對象的屬性時,如果這個對象本身不存在這個屬性,那么就會去它
構造函數的'prototype'屬性中去找*/
fn.showName(); //I'm 小明
fn.showAge(); //And I'm 19
這就是原型,很好理解。那為什么要使用原型呢?
試想如果我們要通過Foo()來創建很多很多個對象,如果我們是這樣子寫的話:
function Foo(name,age){
this.name=name;
this.age=age;
this.showName=function(){
console.log("I'm "+this.name);
}
this.showAge=function(){
console.log("And I'm "+this.age);
}
}
那么我們創建出來的每一個對象,里面都有showName和showAge方法,這樣就會占用很多的資源。
而通過原型來實現的話,只需要在構造函數里面給屬性賦值,而把方法寫在Foo.prototype屬性(這個屬性是唯一的)里面。這樣每個對象都可以使用prototype屬性里面的showName、showAge方法,並且節省了不少的資源。
原型鏈
理解了原型,那么原型鏈就更好理解了。
#####下面這段話可以幫助理解原型鏈
根據要點5,當試圖得到一個對象的屬性時,如果這個對象本身不存在這個屬性,那么就會去它構造函數的’prototype’屬性中去尋找。那又因為’prototype’屬性是一個對象,所以它也有一個’_ _ proto_ _'屬性。
那么我們來看一個例子:
// 構造函數
function Foo(name,age){
this.name=name;
this.age=age;
}
Object.prototype.toString=function(){
//this是什么要看執行的時候誰調用了這個函數。
console.log("I'm "+this.name+" And I'm "+this.age);
}
var fn=new Foo('小明',19);
fn.toString(); //I'm 小明 And I'm 19
console.log(fn.toString===Foo.prototype.__proto__.toString); //true
console.log(fn.__proto__ ===Foo.prototype)//true
console.log(Foo.prototype.__proto__===Object.prototype)//true
console.log(Object.prototype.__proto__===null)//true
是不是覺得有點奇怪?我們來分析一下。
首先,fn的構造函數是Foo()。所以:
fn._ _ proto _ _=== Foo.prototype
又因為Foo.prototype是一個普通的對象,它的構造函數是Object,所以:
Foo.prototype._ _ proto _ _=== Object.prototype
通過上面的代碼,我們知道這個toString()方法是在Object.prototype里面的,當調用這個對象的本身並不存在的方法時,它會一層一層地往上去找,一直到null為止。
所以當fn調用toString()時,JS發現fn中沒有這個方法,於是它就去Foo.prototype中去找,發現還是沒有這個方法,然后就去Object.prototype中去找,找到了,就調用Object.prototype中的toString()方法。
這就是原型鏈,fn能夠調用Object.prototype中的方法正是因為存在原型鏈的機制。
另外,在使用原型的時候,一般推薦將需要擴展的方法寫在構造函數的prototype屬性中,避免寫在_ _ proto _ _屬性里面。
---------------------