我們創(chuàng)建的每個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象玉罐,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。如果按照字面意思來理解脐雪,那么 prototype 就是通過調用構造函數而創(chuàng)建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法恢共。換句話說战秋,不必在構造函數中定義對象實例信息,而是可以將這些信息直接添加到原型對象中讨韭。
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
person2.sayName();
console.log(person1.sayName === person2.sayName);
上面的代碼的輸出是:
- 理解原型對象
原型模式脂信,有點類似于 C++ 的繼承,每個對象都繼承了來自原型對象中的值透硝,這種繼承是只讀的狰闪,我們不能重寫原型對象中的值,如果我們在實例中對原型對象中的同名屬性賦值濒生,其實是在實例中添加了一個新的屬性埋泵,這個屬性會屏蔽來自原型對象中的屬性,而這點就類似于 C++ 中的覆蓋罪治。下面是實例:
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // 來自原型的值
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
person2.sayName(); // 來自實例的值
person1.sayName(); // 來自原型的值
輸出結果:
每當代碼讀取某個對象的某個屬性時丽声,都會執(zhí)行一次搜索,目標是具有給定名字的屬性觉义。搜索首先從對象實例本身開始雁社。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值晒骇;如果沒有找到霉撵,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性洪囤。如果在原型對象中找到了這個屬性徒坡,則返回該屬性的值。
我們已經知道瘤缩,如果我們在實例中對原型對象中的同名屬性賦值崭参,其實是在實例中添加了一個新的屬性,這個屬性會屏蔽來自原型對象中的屬性款咖。即使將這個屬性設置為 null何暮,也只會在實例中設置這個屬性奄喂,而不會恢復其指向原型的連接。不過海洼,使用 delete 操作符則可以完全刪除實例屬性跨新,從而讓我們可以重新訪問原型中的屬性,如下所示:
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
person2.sayName(); // 來自實例的值
delete person2.name;
person2.sayName(); // 來自原型的值
輸出結果:
使用 hasOwnProperty() 方法可以檢測一個屬性是存在于實例中坏逢,還是存在于原型中域帐。這個方法(不要忘記它是從 Object 繼承來的)只在給定屬性存在于對象實例中時,才會返回 true是整。下面是個實例:
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // 來自原型的值
console.log("person1.hasOwnProperty(\"name\") :", person1.hasOwnProperty("name"));
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
console.log("person2.hasOwnProperty(\"name\") :", person2.hasOwnProperty("name"));
person2.sayName(); // 來自實例的值
delete person2.name;
person2.sayName(); // 來自原型的值
console.log("person2.hasOwnProperty(\"name\") :", person2.hasOwnProperty("name"));
實例的輸出結果:
- 原型與 in 操作符
有兩種方式使用 in 操作符:單獨使用和在 for-in 循環(huán)中使用肖揣。在單獨使用時,in 操作符會在通過對象能夠訪問給定屬性時返回 true浮入,無論該屬性存在于實例中還是原型中龙优,下面是使用實例:
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // 來自原型的值
console.log("person1.hasOwnProperty(\"name\"): ", person1.hasOwnProperty("name"));
console.log("\"name\" in person1: ", "name" in person1);
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
console.log("\"name\" in person2: ", "name" in person2);
person2.sayName(); // 來自實例的值
delete person2.name;
person2.sayName(); // 來自原型的值
console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
console.log("\"name\" in person2: ", "name" in person2);
console.log("\"gender\" in person2: ", "gender" in person2);
輸出結果:
同時使用 hasOwnProperty 方法和 in 操作符,就可以確定該屬性到底是存在于對象中事秀,還是存在于原型中彤断。
function hasPrototypeProperty(object, propertyString) {
return !object.hasOwnProperty(propertyString) && (propertyString in object);
}
下面是其使用實例:
function hasPrototypeProperty(object, propertyString) {
return !object.hasOwnProperty(propertyString) && (propertyString in object);
}
function Person() {
}
Person.prototype.name = "Neo";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // 來自原型的值
console.log("person1.hasOwnProperty(\"name\"): ", person1.hasOwnProperty("name"));
console.log("\"name\" in person1: ", "name" in person1);
console.log("hasPrototypeProperty(person1, \"name\"): ", hasPrototypeProperty(person1, "name"));
var person2 = new Person();
person2.name = "Toby";
person2.age = 30;
console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
console.log("\"name\" in person2: ", "name" in person2);
console.log("hasPrototypeProperty(person2, \"name\"): ", hasPrototypeProperty(person2, "name"));
person2.sayName(); // 來自實例的值
delete person2.name;
person2.sayName(); // 來自原型的值
console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
console.log("\"name\" in person2: ", "name" in person2);
console.log("hasPrototypeProperty(person2, \"name\"): ", hasPrototypeProperty(person2, "name"));
其輸出結果如下:
- 更簡單的原型語法
function Person() {
}
Person.prototype = {
name: "Neo",
age: 29,
job: "Teacher",
sayName: function() {
console.log(this.name);
}
}
// 以下代碼,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
- 原型的動態(tài)性
由于在原型中查找值的過程是一次搜索易迹,因此我們對原型對象所做的任何修改都能夠立即從實例上反映出來 —— 即使是先創(chuàng)建了實例后修改原型也照樣如此宰衙,下面是一個實例:
function Person() {
}
var friend = new Person();
Person.prototype.sayHi = function() {
console.log("hi");
}
friend.sayHi();
輸出結果:
但是如果是重寫整個原型對象,那么情況就不一樣了睹欲。我們知道供炼,調用構造函數時會為實例添加一個指向最初原型 [[prototype]] 的指針,而把原型修改為另外一個對象就等于切斷了構造函數與最初原型之間的聯系窘疮。請記拙Ⅱ摺:實例中的指針僅指向原型,而不指向構造函數考余。下面是一個實例:
function Person() {
}
var friend = new Person();
Person.prototype = {
name: "Neo",
age: 29,
job: "Teacher",
sayName: function() {
console.log(this.name);
}
}
// 以下代碼先嬉,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
friend.sayName();
這個例子會報錯:
因為 friend 指向的原型中不包含以 sayName 命名的函數。重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯系楚堤;它們引用的仍然是最初的原型疫蔓。
- 原型對象的問題
原型模式也不是沒有缺點。首先身冬,他省略了為構造函數傳遞初始化參數這一環(huán)節(jié)衅胀,結果所有實例在默認情況下都將取得相同的屬性值。雖然這會在某種程度上帶來一些不方便酥筝,但還不是原型的最大問題滚躯。原型模式的最大問題是由其共享的本性所導致的。
原型中所有屬性是被很多實例共享的,這種共享對于函數非常合適掸掏。對于那些包含基本值的屬性倒也說得過去茁影,畢竟,通過在實例上添加一個同名屬性丧凤,可以隱藏原型中的對應屬性募闲。然而,對于包含引用類型值的屬性來說愿待,問題就比較突出了浩螺。請看下面這個例子:
function Person() {
}
Person.prototype = {
name: "Neo",
age: 29,
job: "Teacher",
friends: ["Toby", "Tina"],
sayName: function() {
console.log(this.name);
}
}
// 以下代碼,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
var person1 = new Person();
var person2 = new Person();
person2.friends.push("Tim");
console.log(person1.friends);
console.log(person2.friends);
console.log(person1.friends === person2.friends);
輸出結果:
在此仍侥,假如我們的初衷就是所有對象共享一個 friends 數組的話要出,是沒有問題的。但是現實中农渊,這樣的情況少之又少患蹂,因此,我們不應該單獨使用原型模式腿时,我們該怎么做呢况脆?請關注下一節(jié)中介紹的內容饭宾。