說明
- 接JavaScript — > [學習筆記]觀察者模式 & 理解對象 & 工廠模式 & 構造函數模式
- 上一篇構造函數模式創建的實例,不同實例的同一個方法是不相等的,為了解決這個問題.出現了原型模式
1. 原型模式
- 具體做法是,不在構造函數中定義對象實例的信息,而是將這些信息直接添加到
原型對象
中
function Person(){}
Person.prototype.name ="Nicholas";
Person.prototype.age =29;
Person.prototype.job ="Software Engineer";
Person.prototype.sayName = function () {alert(this.name);
};
var person1 = new Person();
person1.sayName(); // "Nicholas"var person2 = new Person();
person2.sayName(); // "Nicholas"console.log(person1.sayName == person2.sayName); // true
1.1 理解原型對象
1.1.1 prototype屬性和constructor屬性
- 創建一個函數(Person),就會根據一組規則創建一個prototype屬性(Person.prototype)
- prototype上面還要一個constructor屬性(必帶技能),它(Person.prototype.constructor)指向函數Person
- prototype屬性上的所有屬性和方法會被所有使用new操作符生產的實例(person1, person2)共享
- 實例person1、person2通過
new Person
得到,會有一個[[Prototype]]的指針(在瀏覽器中通過__proto__訪問),它指向Person.prototype
1.1.2 讀取某個屬性的順序
- 讀取屬性的模擬函數如下:
// obj是某個對象, attr是該對象的屬性
const getVal = (obj, attr) =>{// 如果對象中存在該屬性則返回if(obj[attr]) {return obj[attr]} else if (obj.constructor === Object) {// 是Objectreturn undefined} else{// 不是Object,檢查其constructor指向的函數if(obj.constructor[attr]){return obj.constructor[attr]} else {// constructor指向的函數中不存在,順著 __proto__ 屬性找下去.getVal(obj.__proto__, attr);}}
}
說明:
- 所有通過function聲明的函數,都繼承自Function
- Function繼承自Object
1.1.3 說說
通過上面的原型找屬性的順序,可以知道:
- 當使用
new Person
生成實例(person1)的時候,person1會有一個指向Person.prototype的指針__proto__ - 當訪問person1的屬性或方法的時候,先從實例開始尋找,若找到了則返回,否則會順著__proto__向上尋找
1.2 區分一個屬性是否來自原型
需要明白下面2點:
- 使用
in
操作符會返回所有對象上的屬性或者方法 - 使用hasOwnProperty可以確定一個屬性是否來自實例對象
// 判斷一個熟悉是否來自原型
const fromPrototype = (obj, attr) {return !obj.hasOwnprototype && (attr in obj);
}
1.3 Object.defineProperty
Object.defineProperty
: 可以給一個對象添加屬性,并對屬性進行描述- 使用如下:
Object.defineProperty(Person.prototype, "constructor", {enumerable: false,value: Person
})
1.3.1 數據屬性和訪問器屬性
- JS中的屬性類型分為: 數據屬性和訪問器屬性
- 數據屬性包括:
- [[Configurabal]]: 是否能被
delete
刪除 - [[Numerable]]: 是否能被
for-in
循環訪問屬性 - [[Writeble]]: 是否可以被修改
- [[Value]]: 屬性的值
- 訪問器屬性: Configurable、Numerable、Get(讀取時觸發) 、Set(修改時觸發)
1.3.2 Object.defineProperty的使用
- 在使用對象字面量對函數的原型進行賦值的時候,會丟失原本的constructor屬性
- 使用
Object.defineProperty
為它加上constructor屬性
function Person () {}
Person.prototype = {name: 'marron',age: 18
}
Object.defineProperty(Person.prototype, constructor, {numerable: false,value: Person
})
- constructor是不能被
for-in
循環訪問到的 - 在字面量中直接賦值,會被
for-in
循環訪問到
1.4 原型的動態性
- 可以先創建實例后修改原型
function Person () {};
cosnt friend = new Person();
Person.prototype.sayHi = () => { console.log('SayHi') };
friend.sayHi();
- 將上述代碼修改為下面:
- Person.prototype.sayHi = () => { console.log('SayHi') };
+ Person.prototype = {
+ sayHi: function () {
+ console.log('SayHi');
+ }
+ }
- 此時在調用
friend.sayHi
會報錯:Uncaught TypeError: p1.sayHi is not a function
, 原因如下:
- 使用new操作符時: 建立了一個指針 proto, 它指向 Person.prototype(這個是默認建立的),假設在空間A中,即此時 person1 先從自己的空間中取數據,若沒有則去 A中找
- 當對Person.prototype進行字面量賦值時, 它改變的是Person.prototype指針的指向,在上面的一系列操作實際是在空間B中.
- 當完成了Person.prototype的字面量賦值后,相當于給Person的原型對象新開辟了一個空間B.而此時person1的__proto__還是指向A.故訪問不到B
注:重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯系.