function Person() {}
Person.prototype.name = 'kobe'
Person.prototype.age = '23'
Person.prototype.job = 'player'
Person.prototype.sayName = function() {
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.sayName() // 'kobe'
person2.sayName() // 'kobe'
我們創建的每一個函數都有一個prototype屬性,這個屬性指向該函數的原型對象。那麼什麼是原型對象呢?
原型對象就是無論任何時候我們創建了一個函數,這個函數都會有prototype屬性,而這個屬性指向函數的原型對象。也就是說只要創建了一個函數就會自動有一個該函數的原型對象。上面創建了一個Person函數,那麼它的原型對象就是Person Prototype。而原型對象中也有一個constructor屬性指向有prototype屬性的函數。當我們爲這個函數創建了實例person1和person2,person1和person2中就會有一個[[prototype]]屬性指向原型對象。該屬性無法訪問,但是瀏覽器中表現爲_proto_。
圖像表示如下:
當要查找屬性時,先會在實例中查找,如果沒有找到,就會在原型對象中查找,如果找到就返回,如果沒有找到就返回undefined。
例如代碼中的person1.sayName(),首先解析器會問:“person1實例中有sayName屬性嗎”,答:“沒有”。然後繼續搜索,再問:“person1的原型中有sayName屬性嗎”,答:“有”。
如果我們在實例中添加了與原型同名的屬性,那麼實例中的屬性將會屏蔽原型中的那個屬性。
關於原型檢測的相關方法
1.isPrototypeOf()
[[prototype]]指向調用isPrototypeOf()方法的對象,那麼就返回true。
意思就是,[[prototype]]指向一個對象。那是哪個對象呢,就是調用isPrototypeOf()方法的對象。
alert(Person.prototype.isPrototypeOf(person1)) //true
2.getPrototypeOf()
獲取一個對象的原型。
alert(Object.getPrototypeOf(person1)) // Person.prototype
3.hasOwnPrototype()
檢測一個屬性時存在於實例中還是原型中,只在給定屬性存在於實例中才會返回true
alert(person1.hasOwnPrototype("name")) //false
4.in操作符
判斷對象是否能訪問該屬性。不倫屬性是在實例還是原型中,只要能訪問就返回true
alert("name" in person1) //true
alert("sex" in person1) //false
"name"屬性雖然不在實例中,但是在原型中,依然可以訪問到,返回true。但是sex在實例和原型中都不存在,無法訪問,返回false。
對象字面量的形式重寫原型對象
Person.prototype = {
constructor: Person,
name: "kobe",
age: 23,
job: "player",
sayName: function() {
alert(this.name)
}
}
上面每次向原型對象添加屬性都要寫一次Person.prototype,非常繁瑣,所以可以通過一個對象字面量的形式,將屬性都寫在對象字面量裏面。但是這樣寫有一個需要注意的地方,就是這樣寫的話,相當於我們重寫了原型對象,此時Person.prototype中的constructor屬性不再指向Person。而是指向Object構造函數。我們可以通過添加constructor屬性,並賦值爲Person,保證可以訪問到正確的值。
如果我們重寫了原型對象的話,如果我們想要訪問裏面的屬性,那麼我們就必須在重寫原型對象之後實例化對象,因爲重寫原型對象時切斷了現有原型與任何之前已經存在的對象實例之間的聯繫。之前存在的對象實例仍然指向原來的原型對象。
原型對象的缺點:
由於原型中的屬性都是共享的,基本類型的屬性可以通過同名屬性覆蓋,但是我們通過其中一個實例修改了原型中的引用類型的屬性,那麼在其他實例中也是實時更新修改的數據。這或許不是我們想要的結果。
function Person() {}
Person.prototype = {
constructor: Person,
name: "kobe",
age: 23,
job: "player",
friends: ["111","222","333"],
sayName: function() {
console.log(this.name)
}
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push("444")
console.log(person1.friends) // ["111","222","333","444"]
console.log(person2.friends)// ["111","222","333","444"]
也許我們只是想要在person1的friends中添加一個“444”,而person2中不用。
解決方法:
組合使用構造函數模式和原型模式
構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性。好處就是每個實例都會有自己的一份實例屬性副本,同時又共享着對方法的使用。
動態原型模式
function Person(name,age,job) {
//property
this.name = name;
this.age = age;
this.job = job;
//method
if(typeof this.sayName != "function") {
// Person.prototype.sayName = function() {
//console.log(this.name);
//}
Person.prototype = {
sayName: function() {
console.log(this.name);
}
} //這樣的寫函數的方式報錯說沒有 sayName 這個函數
}
}
var person1 = new Person("lhd",23,"lhf")
var person2 = new Person("111",23,"222")
person2.sayName()
person1.sayName() //person1中是沒有sayName這個方法的,所以這裏會報錯
這個模式就是將原型的初始化在構造函數中執行。if條件判斷裏面不需要檢查每個屬性和方法,只要檢查其中一個就可以了。
就是if裏面有多個方法函數。
寄生構造函數模式
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments)
values.toPipedString = function(){
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red", "blue", "green");
console.log(colors.toPipedString()); //"red|blue|green"
目的是給原生的構造函數定義方法,這裏就是給array構造函數添加了一個toPipedString方法,這樣不會對原生構造函數造成污染,減少不必要的麻煩。不建議直接在原生構造函數裏面添加方法。SpecialArray與Array之間的區別就是比Array多了一個方法。
穩妥構造函數模式
穩妥對象:沒有公共屬性,方法也不引用this的對象。
function Person(name,age,job){
var o = new Object();
o.sayName = function() {
alert(name)
},
return o
}
var friends = Person("LHD",23,"player")
friends.sayName();
這裏除了調用sayName方法,沒有其他辦法可以訪問name屬性。