歡迎來個人博客閱讀:「JavaScript 原型中的哲學思想」javascript
記得當年初試前端的時候,學習JavaScript過程當中,原型問題一直讓我疑惑許久,那時候捧着那本著名的紅皮書,看到有關原型的講解時,老是心存疑慮。前端
當在JavaScript世界中走過很多旅程以後,再次萌發起研究這部分知識的慾望,翻閱了很多書籍和資料,才搞懂__proto__
和prototype
的概念。java
故以做此筆記,往後忘了能夠回來看看。若是你看的過程當中以爲理解有些困難,把例子在代碼中跑一跑,親手試一試也許能解決很多疑惑。瀏覽器
卻不知,JavaScript的世界中的對象,追根溯源來自於一個 null函數
「一切皆爲對象」,這句着實是一手好營銷,易記,易上口,印象深入。學習
萬物初生時,一個null
對象,憑空而生,接着Object
、Function
學着null
的模樣塑造了本身,而且它們彼此之間喜結連理,提供了prototype
和constructor
,一個給子孫提供了基因,一個則製造萬千子子孫孫。spa
在JavaScript中,null
也是做爲一個對象存在,基於它繼承的子子孫孫,當屬對象。乍一看,null
像是上帝,而Object
和Function
猶如JavaScript世界中的亞當與夏娃。prototype
__proto__
在JavaScript中,每一個對象都擁有一個原型對象,而指向該原型對象的內部指針則是__proto__
,經過它能夠從中繼承原型對象的屬性,原型是JavaScript中的基因連接,有了這個,才能知道這個對象的祖祖輩輩。從對象中的__proto__
能夠訪問到他所繼承的原型對象。3d
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; // true a.constructor === Foo; // true a.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關係的方法:
instanceof 運算符返回一個布爾值,表示一個對象是否由某個構造函數建立。
Object.isPrototypeOf() 只要某個對象處在原型鏈上,isProtypeOf都返回true
var Bar = function() {} var b = new Bar(); b instanceof Bar // true Bar.prototype.isPrototypeOf(b) // true Object.prototype.isPrototypeOf(Bar) // true
要注意,實例b
的原型是Bar.prototype
而不是Bar
這是一張描述了Object
、Function
以及一個函數實例Foo
他們之間原型之間聯繫。若是理解了上面的概念,這張圖是不難讀懂。
從上圖中,能看到一個有趣的地方。
Function.prototype.__proto__
指向了 Object.prototype
,這說明Function.prototype
是一個 Object
實例,那麼應當是先有的Object
再有Function
。
可是Object.prototype.constructor.__proto__
又指向了 Function.prototype
。這樣看來,沒有Function
,Object
也不能建立實例。
這就產生了一種類「先有雞仍是先有蛋」的經典問題,究竟是先有的Object
仍是先有的Function
呢?
這麼哲學向的問題,留給你思考了。
我只是感慨:越往JavaScript的深處探索,越以爲這一門語言很哲學。
update on 2017/01/05
時隔半年,偶爾翻開這篇文章。
對於這個問題,又有了新的思考。
願意跟能看到這裏的你來分享一下。
咱們能夠先把 Object.prototype
和 Function.prototype
這兩個拎出來看,由於他們自己就是一個實例對象。
爲方便理解,咱們改一下名字,避免和 Object 和 Function 的強關聯,分別叫:Op
和 Fp
那麼就有這樣的原型鏈存在了
我再描述一下上面的原型鏈,先有 null , 再有了 Op , 而後再有了 Fp ,而後以 Fp 爲原型的兩個構造函數 (Object, Function) 出現了。 而做爲構造函數,須要有個 prototype 屬性用來做爲以該構造函數創造的實例的繼承。 因此Object.prototype = Op, Function.prototype = Fp。