JS物件導向程式設計(三):原型

justcool發表於2018-11-23

一、prototype(原型)是什麼?

 在JavaScript中,我們每建立一個函式,都會有一個prototype(原型)屬性:這個屬性是一個指標,指向一個物件,物件的用途是包含可以由特定型別的所有例項共享的屬性和方法

JS物件導向程式設計(三):原型

圖3-1

prototype 就是通過呼叫建構函式而建立的那個物件例項的原型物件。使用原型可以讓所有物件例項共享它所包含的屬性和方法

function Person(){}

Person.prototype.name = "lilei"; 
Person.prototype.age = 26; 
Person.prototype.sayHello = function(){ 
    console.log(this.name+":Hello!"); 
}; 
var person1 = new Person(); 
person1.sayHello(); //"lilei:hello" 

var person2 = new Person(); 
person2.sayHello(); //"lilei:hello" 
console.log(person1.sayHello == person2.sayHello); //true 
複製程式碼

二、原型、建構函式、例項物件之間的關係:

JS物件導向程式設計(三):原型

圖3-2

 所有原型物件都會自動獲得一個 constructor(建構函式)屬性,這個屬性包含一個指向 prototype 屬性所在函式的指標。如圖3-1:Person.prototype.constructor 指向 Person。

Person.prototype.constructor === Person // true
複製程式碼

 例項化的物件內部有一個看不見的__proto__指標,指向原型物件,__proto__只在瀏覽器內部使用,__對指令碼則是完全不可見的。雖然在所有實現中都無法訪問到__proto__,但可以通過 isPrototypeOf()方法來確定物件之 間是否存在這種關係:

console.log(Person.prototype.isPrototypeOf(person1)); //true 
console.log(Person.prototype.isPrototypeOf(person2)); //true 
複製程式碼

 所有物件的__proto__都指向其建構函式的prototype

person1.__proto__  === Person.prototype //true
複製程式碼

三、__proto__與prototype的關係

  1. 所有建構函式的__proto__都指向Function.prototype,它是一個空函式(Empty function)
Person.__proto__ === Function.prototype     // true
Number.__proto__ === Function.prototype     // true
Boolean.__proto__ === Function.prototype    // true
String.__proto__ === Function.prototype     // true
Object.__proto__ === Function.prototype     // true
Function.__proto__ === Function.prototype   // true
Array.__proto__ === Function.prototype      // true
RegExp.__proto__ === Function.prototype     // true
Error.__proto__ === Function.prototype      // true
Date.__proto__ === Function.prototype       // true
複製程式碼

所以所有建構函式都繼承了Function.prototype的屬性及方法

  1. Function.prototype也是唯一一個typeof 為 “function”的prototype。其它的建構函式的prototype都是一個物件
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object
console.log(typeof Object.prototype)   // object
複製程式碼
  1. 除Object之外所有建構函式的原型的__proto__都指向Object.prototype
Function.prototype.__proto__ === Object.prototype   // true
Person.prototype.__proto__ === Object.prototype     // true
Number.prototype.__proto__ === Object.prototype     // true
Boolean.prototype.__proto__ === Object.prototype    // true
String.prototype.__proto__ === Object.prototype     // true
Array.prototype.__proto__ === Object.prototype      // true
Function.prototype.__proto__ === Object.prototype   // true
RegExp.prototype.__proto__ === Object.prototype     // true
Error.prototype.__proto__ === Object.prototype      // true
Date.prototype.__proto__ === Object.prototype       // true
複製程式碼

 Object.prototype的__proto__?

Object.prototype.__proto__ === null  // true
複製程式碼

四、例項屬性與原型屬性

  1. 原型包含 constructor 屬性,而該屬性也是共享的,因此可以通過物件例項訪問。
person1.constructor === Person.prototype.constructor    //true
person1.constructor === Person                          //true
person1.name === Person.prototype.name                  //true
person1.age === Person.prototype.age                    //true
person1.sayHello ===  Person.prototype.sayHello         //true
複製程式碼
  1. 雖然可以通過物件例項訪問儲存在原型中的值,但卻不能通過物件例項重寫原型中的值:重寫的屬性是儲存在例項上的屬性
function Person(){}

Person.prototype.name = "lilei"; 
Person.prototype.age = 26; 
Person.prototype.sayHello = function(){ 
    console.log(this.name+":Hello!"); 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.name = "Greg"; 
console.log(person1.name);    // "Greg" ——來自例項
console.log(person2.name);    // "lilei" ——來自原型
複製程式碼

訪問person1.name時會在這個例項上搜尋一個名為 name的屬性。這個屬性如果存在,就返回,不存在 就到原型上去找。當為物件例項新增一個屬性時,這個屬性就會遮蔽原型物件中儲存的同名屬性;新增這 個屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性

  1. 使用 delete 操作符則可以刪除例項屬性,從而讓我們能夠重新訪問原型中的屬性
function Person(){}

Person.prototype.name = "lilei"; 
Person.prototype.age = 26; 
Person.prototype.sayHello = function(){ 
    console.log(this.name+":Hello!"); 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.name = "Greg"; 
console.log(person1.name);    // "Greg" ——來自例項
console.log(person2.name);    // "lilei" ——來自原型

delete person1.name; 
console.log(person1.name);    // "lilei" ——來自原型
複製程式碼
  1. 通過hasOwnProperty()方法,可以判斷訪問屬性是否為 例項屬性
person1.name = "Greg"; 
person1.hasOwnProperty("name")  //true
person2.hasOwnProperty("name")  //false
複製程式碼

 當 person1 重寫name 屬性後返回 true,因為這時候 name 是一個例項屬性,而非原型屬性

  1. 通過in可以判斷物件是否具有某個屬性,包括物件例項及其原型的屬性;
person1.name = "Greg"; 
person1.hasOwnProperty("name")  //true
person2.hasOwnProperty("name")  //false
console.log("name" in person1)  //true
console.log("name" in person2)  //true
複製程式碼
  1. 通過在例項上新增一個同名屬性,可以隱藏原型中的對應屬性。但是,對於引用型別來說,在未重寫的情況下,修改引用型別值的屬性,就是在原型屬性值的基礎上修改 原型屬性值的屬性
function Person(){}

Person.prototype = { 
    constructor: Person, 
    name : "lilei", 
    age : 26, 
    friends : ["Jim", "Tom"]
};
var person1 = new Person(); 
var person2 = new Person(); 

person1.friends.push("hanmeimei"); 

console.log(person1.friends); //"Jim,Tom,hanmeimei" 
console.log(person2.friends); //"Jim,Tom,hanmeimei" 
console.log(person1.friends === person2.friends); //true 
複製程式碼

每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含一個指向原型 物件的內部指標。如果我們讓原型物件等於另一個型別的例項,會發生什麼?所以下一節《繼承》


文章參考:

《JavaScript 高階程式設計》中文譯本 第三版

相關文章