JavaScript 原型中的哲學思想

歡迎來個人博客閱讀:「JavaScript 原型中的哲學思想」javascript

記得當年初試前端的時候,學習JavaScript過程當中,原型問題一直讓我疑惑許久,那時候捧着那本著名的紅皮書,看到有關原型的講解時,老是心存疑慮。前端

當在JavaScript世界中走過很多旅程以後,再次萌發起研究這部分知識的慾望,翻閱了很多書籍和資料,才搞懂__proto__prototype的概念。java

故以做此筆記,往後忘了能夠回來看看。若是你看的過程當中以爲理解有些困難,把例子在代碼中跑一跑,親手試一試也許能解決很多疑惑。瀏覽器

一切皆爲對象

卻不知,JavaScript的世界中的對象,追根溯源來自於一個 null函數

「一切皆爲對象」,這句着實是一手好營銷,易記,易上口,印象深入。學習

萬物初生時,一個null對象,憑空而生,接着ObjectFunction學着null的模樣塑造了本身,而且它們彼此之間喜結連理,提供了prototypeconstructor,一個給子孫提供了基因,一個則製造萬千子子孫孫。spa

在JavaScript中,null也是做爲一個對象存在,基於它繼承的子子孫孫,當屬對象。乍一看,null像是上帝,而ObjectFunction猶如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.getPrototypeofObject.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屬性並不屬於aa.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 // true
Bar.prototype.isPrototypeOf(b) // true
Object.prototype.isPrototypeOf(Bar) // true

要注意,實例b的原型是Bar.prototype而不是Bar

一張歷史悠久的圖

圖片描述

這是一張描述了ObjectFunction以及一個函數實例Foo他們之間原型之間聯繫。若是理解了上面的概念,這張圖是不難讀懂。

從上圖中,能看到一個有趣的地方。

  1. Function.prototype.__proto__ 指向了 Object.prototype,這說明Function.prototype 是一個 Object實例,那麼應當是先有的Object再有Function

  2. 可是Object.prototype.constructor.__proto__ 又指向了 Function.prototype。這樣看來,沒有FunctionObject也不能建立實例。

這就產生了一種類「先有雞仍是先有蛋」的經典問題,究竟是先有的Object仍是先有的Function呢?
這麼哲學向的問題,留給你思考了。

我只是感慨:越往JavaScript的深處探索,越以爲這一門語言很哲學。

先有雞仍是先有蛋?

update on 2017/01/05

時隔半年,偶爾翻開這篇文章。
對於這個問題,又有了新的思考。
願意跟能看到這裏的你來分享一下。

咱們能夠先把 Object.prototypeFunction.prototype 這兩個拎出來看,由於他們自己就是一個實例對象。
爲方便理解,咱們改一下名字,避免和 Object 和 Function 的強關聯,分別叫:OpFp

那麼就有這樣的原型鏈存在了

先有雞仍是先有蛋

我再描述一下上面的原型鏈,先有 null , 再有了 Op , 而後再有了 Fp ,而後以 Fp 爲原型的兩個構造函數 (Object, Function) 出現了。 而做爲構造函數,須要有個 prototype 屬性用來做爲以該構造函數創造的實例的繼承。 因此Object.prototype = Op, Function.prototype = Fp。

相關文章
相關標籤/搜索