在js中,建立對象的方式有工廠模式和構造函數模式等; 而構造函數模式最大的問題在於:構造函數中的每一個方法都須要在實例對象中從新建立一遍,不能複用,因此爲了解決這一個問題,就須要使用原型模式來建立對象。
原型模式是把全部實例共享的方法和屬性放在一個叫作prototype(原型)的屬性中 ,在建立一個函數時都會有個prototype屬性, 這個屬性是一個指針,指向一個對象,是經過調用構造函數而建立的那個對象實例的原型對象。數組
// 構造函數 function Person() {}; // 原型屬性prototype Person.prototype.name = '張三'; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); //張三 let person2 = new Person(); person2.sayName(); // 張三 console.log(person1.sayName == person2.sayName); //true
不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象,在默認的狀況下,全部的原型對象都自動得到一個constructor(構造函數)屬性,這是一個指針,指向prototype屬性所在的函數。建立了自定義的構造函數以後,其原型對象默認只會取得constructor屬性;其餘的方法則是從Object繼承來的。
當調用構造函數建立一個新實例對象後,該實例的內部將包含一個指針[[Prototype]],指向構造函數的原型對象。這個鏈接存在於實例和構造函數的原型對象之間,而不是存在實例和構造函數之間。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若是在實例中找到了就返回該屬性的值,沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性,若是在原型對象中找到了這個屬性,就返回該屬性的值。
雖然能夠經過實例訪問保存在原型中的值,但不能經過實例對象重寫原型中的值,若是在實例中添加一個在原型中的同名屬性,該屬性會自動屏蔽原型中的屬性,可是不會修改原型中的屬性,只會阻止訪問原型中的屬性,經過delete操做符則能夠徹底刪除實例屬性,使得能夠從新訪問原型中的屬性。ide
原型與in操做符函數
hasOwnProperty()方法能夠檢測一個屬性是否存在於實例對象中,
// 構造函數
function Person() {
this.age = 16;
};
Person.prototype.name = "張三";
let person1 = new Person();
console.log(person1.hasOwnProperty('name')); // false
console.log(person1.hasOwnProperty('age')); // truethisin操做符的使用能夠分爲兩類,單獨使用和在for-in循環使用,在單獨使用時,in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在於實例中仍是原型中。
// 構造函數
function Person() {}
Person.prototype.name = 'zhang';
let person1 = new Person();
console.log('name' in person1); // true
person1.age = 14;
console.log('age' in person1); // true
同時使用hasOwnProperty()方法和in操做符,能夠肯定該屬性時在原型上仍是在存在於對象中。
// 構造函數
function Person() {}
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
Person.prototype.name = "張三";
let person = new Person();
console.log(hasPrototypeProperty(person, 'name')); // true
console.log(hasPrototypeProperty(person, 'age')); // false
使用for-in循環時,返回的是全部可以經過對象訪問的、可枚舉的屬性,其中即包含存在於實例中的屬性,也包含與存在原型中的屬性。
let o = {
name: 'san',
age: 14,
};
for(let key in o) {
console.log(key);
}
要取得對象上全部可枚舉的實例屬性,可使用Object.keys()方法,接收一個對象做爲參數,返回一個包含全部可枚舉屬性的字符串數組。
若是想獲得全部實例屬性。不管是否可枚舉,均可以使用Object.getOwnPropertyNames()方法。prototype
更簡單的原型語法指針
爲了減小沒必要要的輸入和從視覺上更好的封裝原型的功能,常見的作法是用一個包含全部屬性和方法的對象字面量來重寫整個原型對象。
// 構造函數
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '張三',
};
經過這個方式會致使原型對象中的constructor屬性不在指向Person了。若是constructor的值真的很重要,能夠像下面這樣特地將它設置回適當的值。
// 構造函數
function Person() {};
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
},
name: '張三',
};
可是經過這種方式會致使對象的[[Enumerable]]特性被設置爲ture,默認狀況下,constructor屬性時不可枚舉的,能夠經過Object.defineProperty()解決這個問題。
// 構造函數
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '張三',
};
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
}code
原型的動態性對象
當對原型對象所作的任何修改都可以當即從實例上反應出來。
function Person() {};
var friend = new Person();
Person.prototype.sayHi = function() {
console.log('hi');
};
friend.sayHi(); // hi 繼承可是若是是重寫整個原型對象,那麼狀況就不同了。調用構造函數時會爲實例添加一個指向最初原型的[[prototype]]指針,而把原型修改成另一個對象 就至關於切斷了構造函數與最初原型之間的聯繫。 實例中的指針僅指向原型,而不是指向構造函數。
// 構造函數
function Person() {};
var friend = new Person();
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
}
};
friend.sayHi(); // Uncaught TypeError: friend.sayHi is not a function字符串建立了一個Person的實例,而後又重寫了其原型對象。可是在使用sayHi()時發生了錯誤,這個時候實例所指向的原型對象是一個新的對象。重寫原型對象切斷了現有原型與以前已經存在的對象實例直接的聯繫。因此報錯了。
原型模式的重要性不只體如今建立自定義類型方面,就連全部原生的引用類型,都採用這種模式,全部的原生引用類型(Object、Array、String)等,都在其構造函數的原型上定義了方法。能夠像修改自定義對象的原型同樣修改原生對象的原型。
對於包含引用類型值的屬性來講,全部實例在默認的狀況下都會取得相同的屬性值。
// 構造函數
function Person() {};
// 原型屬性prototype
Person.prototype = {
constructor: Person,
friends: ['張三', '李四'],
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push('王五');
console.log(person1.friends); // ["張三", "李四", "王五"]
console.log(person2.friends); // ["張三", "李四", "王五"]因爲friends存在於Person的原型對象中,因此person1對friends的修改也會經過person2反應出來,可是實例對象通常都是要有屬於本身的所有屬性,正由於如此,不多有人單獨使用原型模式來建立對象。