一切皆為對象
殊不知,JavaScript的世界中的對象,追根溯源來自于一個 null
「一切皆為對象」,這句著實是一手好營銷,易記,易上口,印象深刻。
萬物初生時,一個null對象,憑空而生,接著Object、Function學著null的模樣塑造了自己,并且它們彼此之間喜結連理,提供了prototype和constructor,一個給子孫提供了基因,一個則制造萬千子子孫孫。
在JavaScript中,null也是作為一個對象存在,基于它繼承的子子孫孫,當屬對象。乍一看,null像是上帝,而Object和Function猶如JavaScript世界中的亞當與夏娃。
原型指針 proto
在JavaScript中,每個對象都擁有一個原型對象,而指向該原型對象的內部指針則是proto,通過它可以從中繼承原型對象的屬性,原型是JavaScript中的基因鏈接,有了這個,才能知道這個對象的祖祖輩輩。從對象中的proto可以訪問到他所繼承的原型對象。
var a = new Array();
a.proto === Array.prototype // true
上面代碼中,創建了一個Array的實例a,該實例的原型指向了Array.prototype。
Array.prototype本身也是一個對象,也有繼承的原型:
a.proto.proto === Object.prototype // true// 等同于 Array.prototype.proto === Object.prototype
這就說了明了,Array本身也是繼承自Object的,那么Object的原型指向的是誰呢?
a.proto.proto.proto === null // true// 等同于 Object.prototype.proto === null
所以說,JavaScript中的對象,追根溯源都是來自一個null對象。佛曰:萬物皆空,善哉善哉。
除了使用.proto方式訪問對象的原型,還可以通過Object.getPrototypeOf方法來獲取對象的原型,以及通過Object.setPrototypeOf方法來重寫對象的原型。
值得注意的是,按照語言標準,proto屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前后的兩根下劃線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該盡量少用這個屬性,而是用 Object.getPrototypeof和Object.setPrototypeOf,進行原型對象的讀寫操作。這里用proto屬性來描述對象中的原型,是因為這樣來得更加形象,且容易理解。
原型對象 prototype
函數作為JavaScript中的一等公民,它既是函數又是對象,函數的原型指向的是Function.prototype
var Foo = function() {}
Foo.proto === Function.prototype // true
函數實例除了擁有proto屬性之外,還擁有prototype屬性。通過該函數構造的新的實例對象,其原型指針proto會指向該函數的prototype屬性。
var a = new Foo();
a.proto === Foo.prototype; // true
而函數的prototype屬性,本身是一個由Object構造的實例對象。
Foo.prototype.proto === Object.prototype; // true
prototype屬性很特殊,它還有一個隱式的constructor,指向了構造函數本身。
Foo.prototype.constructor === Foo; // truea.constructor === Foo; // truea.constructor === Foo.prototype.constructor; // true
PS: a.constructor屬性并不屬于a(a.hasOwnProperty("constructor") === false),而是讀取的a.proto.constructor,所以上圖用虛線表示a.constructor,方便理解。
原型鏈
概念:原型鏈作為實現繼承的主要方法,其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
每個構造函數都有一個原型對象(prototype),原型對象都包含一個指向構造函數的指針(constructor),而實例都包含一個指向原型對象的內部指針(proto)。
那么,假如我們讓原型對象等于另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含著一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立。如此層層遞進,就構造了實例與原型的鏈條,這就是原型鏈的基本概念。
意義:“原型鏈”的作用在于,當讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。以此類推,如果直到最頂層的Object.prototype還是找不到,則返回undefine。
親子鑒定
在JavaScript中,也存在鑒定親子之間DNA關系的方法:
1、instanceof 運算符返回一個布爾值,表示一個對象是否由某個構造函數創建。
2、Object.isPrototypeOf() 只要某個對象處在原型鏈上,isProtypeOf都返回true
var Bar = function() {}var b = new Bar();
b instanceof Bar // trueBar.prototype.isPrototypeOf(b) // trueObject.prototype.isPrototypeOf(Bar) // true
要注意,實例b的原型是Bar.prototype而不是Bar
一張歷史悠久的圖
這是一張描述了Object、Function以及一個函數實例Foo他們之間原型之間聯系。如果理解了上面的概念,這張圖是不難讀懂。
從上圖中,能看到一個有趣的地方。
1、Function.prototype.proto 指向了 Object.prototype,這說明Function.prototype 是一個 Object實例,那么應當是先有的Object再有Function。
2、但是Object.prototype.constructor.proto 又指向了 Function.prototype。這樣看來,沒有Function,Object也不能創建實例。
這就產生了一種類「先有雞還是先有蛋」的經典問題,到底是先有的Object還是先有的Function呢?
這么哲學向的問題,留給你思考了。