原型與原型鏈 javascript 創建對象 類與構造函數是大多數編程語言所擁有的,而借鑒了 C 與 JAVA 的 javascript 也是有類和構造函數的,不過 javascript 的實現不太一樣。 上面的例子中, 就是構造函數, 就是它的實例,但是它對共用性不太好,所以有了原型模式。 原型模 ...
原型與原型鏈
javascript 創建對象
類與構造函數是大多數編程語言所擁有的,而借鑒了 C 與 JAVA 的 javascript 也是有類和構造函數的,不過 javascript 的實現不太一樣。
// before ES6
// 構造函數模式
function Person(name){
this.name = name;
this.sayName = function() {
console.log(this.name);
}
}
var p = new Person();
console.log(p instanceof Person); // true
上面的例子中,Person
就是構造函數,p
就是它的實例,但是它對共用性不太好,所以有了原型模式。
// 原型模式
function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
p1.sayName(); // kobe
p2.sayName(); // kobe
原型模式創建的實例都可以共用Person.prototype
的屬性和方法,而prototype
是 javascript 實現類的一個關鍵所在。
prototype
在 javascript 中,每個函數都有一個prototype
屬性,這個屬性指向函數的原型對象,上面的例子,可以用下圖解釋,圖中用Person.prototype
表示 Person 的原型對象。
constructor
Person 是構造函數,有prototype
指針指向原型對象,而Person.prototype
也有一個constructor
屬性指向構造函數。
簡單來說,就是 javascript 會在函數創建的時候為函數添加一個prototype
屬性,這個屬性是指向函數的原型對象的指針,而原型對象也會獲得一個constructor
屬性,這個屬性是一個指向函數本身的指針,即指向構造函數。
function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
console.log(this.name);
}
Person.prototype.constructor == Person; // true
__proto__
函數有prototype
,那對象呢?其實對象內部也有一個[[prototype]]
指針,這個指針指向創建實例對象的構造函數的原型對象。
在 ES5 之前的語言標準里,是不允許訪問這個屬性的,但瀏覽器都實現了一個__proto__
對[[prototype]]
進行訪問操作,所以 ES5 增加了一個Object.getPrototypeOf()
方法來訪問[[prototype]]
。
Object.getPrototypeOf(object)
方法返回指定對象的原型(內部[[Prototype]]
屬性的值)。在 ES5 中,如果參數不是一個對象類型,將拋出一個 TypeError 異常。在 ES6 中,參數會被強制轉換為一個 Object。
建議在代碼中使用Object.getPrototypeOf()
來訪問內部[[Prototype]]
屬性,不建議使用非標準的__proto__
,為了方便區別,文中使用__proto__
來表示[[prototype]]
。
回到上面的例子,當調用 Person 來創建實例的時候,該實例會獲得一個__proto__
指針指向構造函數的原型對象。
正是因為創建的 p1、p2 實例都擁有__proto__
指向構造函數(Person)的原型對象(Person.prototype),它們才能共用Person.prototype
的屬性和方法。
function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
console.log(this.name);
}
let p1 = new Person();
let p2 = new Person();
Object.getPrototypeOf(p1) == Object.getPrototypeOf(p2); // true
簡單來說,就是在 javascript 中,每一個對象(除null)在創建的時候都會將其與另一個對象進行關聯,而關聯的這個對象就是所謂的原型。
使用isPrototypeOf()
方法就能確定這種關聯:
Person.prototype.isPrototypeOf(p1); // true
Person.prototype.isPrototypeOf(p2); // true
屏蔽性
還是這個例子:
function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
console.log(this.name);
}
let p1 = new Person();
// 訪問 p1 的 name
p1.name; // kobe
當我們通過實例 p1 訪問name
屬性的時候,javascript 引擎會先在實例 p1 中查找name
,當發現 p1 沒有這個屬性,就會通過__proto__
向它指向的原型對象進行查找,找到就返回這個屬性的值,也就是說訪問name
執行了兩次查找。
如果 p1 自己添加了name
屬性,就會在第一次查找時返回值:
// 在 p1 中添加 name
p1.name = 'jordan';
// 只會執行一次查詢
p1.name; // jordan
// 原型中的值並未改變
Object.getPrototypeOf(p1).name; // kobe
也就是說,在實例上添加name
屬性後,該屬性就會屏蔽原型中的那個屬性,但並不會改變原型中的該屬性的值。
原型鏈
既然在 javascript 中,所有對象(除null)都有__proto__
,而實例 p1 指向Person.prototype
,那Person.prototype
對象的__proto__
又指向哪裡呢?
Object.getPrototypeOf(Person.prototype) == Object.prototype; // true
答案是Object.prototype
,因為原型對象是通過構造函數 Object 來創建的,所以它指向Object.prototype
。
那 Object.prototype 的__proto__
又指向哪裡呢?它指向這條鏈的終點:null,代表無。
Object.getPrototypeOf(Object.prototype); // null
回到上面所說的查找name
屬性,如果在第二次都沒有查找到name
屬性,就會沿著這條鏈繼續向上查找,直到終點都沒有找到,就會返回 undefined。
function Person(){}
let p1 = new Person();
p1.name; // undefined
上圖中虛線部分由原型相互關聯組成的鏈狀結構就是所謂的原型鏈。
備註
不得不說,《javascript高級程式設計》這本書把原型講得很透徹,今天再看一遍依然感覺通透。