前言
JavaScript 中的原型一直是我很懼怕的一個主題,理由很簡單,因為真的不好理解,但它確實是 JavaScript 中很重要的一部分,而且是面試的必考題,就算現在不懂,以後遲早有一天要把它弄懂,不然的話永遠都沒辦法把自己的技術能力往上提高一個層次,所以今天就來講講 JavaScript 中的原型。
本文是這系列的第四篇,往期文章:
什麼是原型
首先要說一下為什麼會有原型這個東西,那是因為在 JavaScript 中並沒有 “類” 的概念,它是靠原型和原型鏈實現物件屬性的繼承,即便在 ES6 中新出了class
的語法,但那也只是一個語法糖,它的底層依然是原型。
要理解原型(原型鏈),最重要的是理解兩個屬性以及它們之間的關係:
__proto__
prototype
__proto__
JavaScript中,萬物皆物件,所有的物件都有__proto__
屬性(null
和undefined
除外),而且指向創造這個物件的函式物件的prototype
屬性。
var obj = {};
console.log( obj.__proto__ === Object.prototype ); // true
var arr = [];
console.log( arr.__proto__ === Array.prototype ); // true
var fn = function(){};
console.log( fn.__proto__ === Function.prototype ); // true
var str = "";
console.log( str.__proto__ === String.prototype ); // true
var num = 1;
console.log( num.__proto__ === Number.prototype ); // true
複製程式碼
前面說了,在 JavaScript 中,一切皆物件(可以理解為它們都是從物件那裡繼承過來的),所以:
console.log( Function.prototype.__proto__ === Object.prototype ); // true
console.log( Array.prototype.__proto__ === Object.prototype ); // true
console.log( String.prototype.__proto__ === Object.prototype ); // true
複製程式碼
而因為Object.prototype
的__proto__
已經是終點了,所以它的指向是:
console.log( Object.prototype.__proto__ === null ); // true
複製程式碼
注意,雖然大多數瀏覽器都支援通過__proto__
來訪問,但它並不是ECMAScript
的標準,在 ES5 中可以通過Object.getPrototypeOf()
來獲取這個屬性。
var obj = {};
console.log( Object.getPrototypeOf(obj) === Object.prototype ); // true
複製程式碼
prototype
prototype
是每個函式物件都具有的屬性(它也有__proto__
,因為函式也是物件),例項化建立出來的物件會共享此prototype
裡的屬性和方法(通過__proto__
)。
在上面的例子中已經看到過prototype
的身影,下面通過一個例子來講述它的作用。
現在我們有一個建構函式Person
,並且對它進行例項化:
function Person(name){
this.name = name;
this.sayName = function(){
console.log("我的名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName(); // 我的名字是:小明
b.sayName(); // 我的名字是:小紅
複製程式碼
new運算子的缺點
但是,用建構函式生成例項物件,有一個缺點,那就是無法共享屬性和方法。
例如上面例子中的a
和b
,它們都有sayName
方法,雖然做的事相同,但它們卻是獨立的,這就會造成極大的資源浪費,因為每一個例項物件,都有自己的屬性和方法的副本。
prototype屬性的引入
考慮到這一點,Brendan Eich 決定為建構函式設定一個prototype
屬性。
這個屬性包含一個物件,所有例項物件需要共享的屬性和方法,都放在這個物件裡面,而不需要共享屬性和方法,就放在建構函式裡面,這個物件就是prototype
物件。
例項物件一旦建立,將自動引用prototype
物件的屬性和方法。也就是說,例項物件的屬性和方法,分成兩種,一種是本地的,另一種是引用的。
現在對上面的例子進行改寫:
function Person(name){
this.name = name;
}
Person.prototype = {
sayName : function(){
console.log("我的名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName() // 我的名字是:小明
b.sayName() // 我的名字是:小紅
複製程式碼
現在無論Person
被例項化多少次,它的例項物件都共享同一個sayName
方法,這就是prototype
最大的用處。
原型鏈
講原型一個不可避免的概念就是原型鏈,原型鏈是通過__proto__
來實現的。
現在我們以Person
的例子來講整個原型鏈。
var a = new Person("小明");
// 例項化物件的 __proto__ 指標指向建構函式的原型
console.log( a.__proto__ === Person.prototype )
// 建構函式的原型是一個物件,它的 __proto__ 指向物件的原型
console.log( Person.prototype.__proto__ === Object.prototype )
// 函式也是一個物件,它的 __proto__ 指向 函式的原型
console.log( Person.__proto__ === Function.prototype )
// 函式的原型是一個物件,它的 __proto__ 指向物件的原型
console.log( Function.prototype.__proto__ === Object.prototype )
// 物件的原型的__proto__ 指向 null
console.log( Object.prototype.__proto__ === null )
複製程式碼
以上就是a
物件的整個原型鏈。
屬性查詢機制
當訪問一個物件的屬性時,Javascript 會從物件本身開始往上遍歷整個原型鏈,直到找到對應屬性為止。如果此時到達了原型鏈的頂部,也就是上例中的 Object.prototype
,仍然未發現需要查詢的屬性,那麼 Javascript 就會返回 undefined
值。
注:此文為原創文章,如需轉載,請註明出處。