1. 程式人生 > >深入剖析js的原型Function和Object之間的關係

深入剖析js的原型Function和Object之間的關係

我看了很多網上有關於原型的一些說明,總覺得說的不夠清楚,而有一句話很有意思“在js裡萬物皆物件”,我承認事實確實是這樣,但是都說的模稜兩可,現在我們就深入探究一下這裡面的門道(我使用的火狐瀏覽器開發版,不同瀏覽器可能控制檯輸出不一樣)

function fun1(name) {
	this.name = name;
}

console.log("fun1", fun1)

 從結果可以看到定義一個function,它裡邊所含有的內容這六個屬性是每個function所必有的,直接看第五個prototype(注意prototype是一個物件)就是傳說中的原型(本文只稱它為prototype),第六個屬性是灰色的並且用尖括號括起來,它這麼不顯眼是因為js就不想讓程式設計師去用它,在以前版本的瀏覽器它有另外一個名字叫__proto__(為了方便區分,下文就以__proto__來稱呼它,而且在以前的版本里名字是__proto__,以下劃線開頭還是兩個說明是js堅決拒絕程式設計師去修改它的,但是下面為了剖析其內部原理我會對其做一些粗暴的改變,大家注意在工程中儘量避免)。

如果大家去實驗一下就會發現,每個物件都會有__proto__這個屬性,但一般情況下只有宣告function的變數(例如上圖中的fun1)才會有(自動生成)prototype這個屬性,而function通過在它的名字前加new 可以創建出屬於它的例項,因此我認為js裡的function有三個角色:函式,物件和類(類似於Java裡的物件可以通過類產生,有人說js和java沒有半毛錢關係,我想說它們都是面向物件的語言,都有物件這個概念,那麼伴隨的也就有類,function宣告的變數就是類)。而prototype這個就體現了function類的概念

可以看到在 prototype裡有兩個屬性constructor和__proto__,在前面我們說過prototype是一個物件和每個物件都會有__proto__這個屬性

,因此prototype也是有__proto__這個屬性;constructor(構造方法)這個屬性是在生成prototype時自動生成的屬性,其指向函式本身(在申明函式時,js自動建立該函式的peototype屬性)。

function fun1(name) {
	this.name = name;
}

console.log("fun1", fun1)
console.log(fun1.prototype.constructor === fun1)

一個物件__proto__指向產生它的類的prototype(就是指向new 是後邊所跟的那個東西,嚴格來講是它作為左值時等號右邊的東西,function fun1(){}等價於var fun1 = function(){},fun1的__proto__指向Function的prototype)。

function fun1(name) {
	this.name = name;
}

var temp = new fun1("");
var obj = new Object;
console.log(temp.__proto__ === fun1.prototype)
console.log(fun1.__proto__ === Function.prototype)
console.log(obj.__proto__ === Object.prototype)

 

總結一下:

  1. 所有物件都有__proto__屬性,是用來通過__proto__找到它的原型即prototype,function宣告的變數的__proto__指向Function的prototype,其它物件的__proto__指向Object的prototype
  2. function宣告的變數、Function和Object都有prototype, 有prototype的東西可以產生例項(即可以作為new 後邊的值)。 
  3. prototype它是一個物件(在宣告函式變數是在函式內部由js自動建立),因此它也有__proto__,並且指向Object的prototype。

Function和Object非常特殊,我們來具體分析

首先看一下Function裡的東西

console.log("Function", Function);
console.log("Function.__proto__", Function.__proto__)

 從控制檯的列印可以明顯的看出Function的__proto__指向了它自己的prototype

接下里看Object(Object裡有很多其它的東西,為了簡潔我直接列印Object的prototype)

console.log("Object.prototype", Object.prototype)
console.log("Object.__proto__", Object.__proto__)

Object的prototype和Function的prototype的__proro__指向是相同的如下圖:

綜上可以看出Object的__proto__指向Function的prototype,而Object的prototype並沒有灰色的<prototype>即__proto__,即它是一切之源。

console.log("Object.prototype.__proto__", Object.prototype.__proto__)

 我將Object的prototype稱為源型,下面我給出我個人對這些現象的解釋:

源型是js的內建的一段程式碼,所有所有通過這個源型創造出的都是object,第一步先創造出Function的prototype,因此這個prototype的__proto__指向源型,然後再通過這個prototype造出Function,因此Function的__proto__指向它自己的prototype,然後用Function造出Object,因此Object的__proto__指向Function的prototype,然後js直接將Object的prototype替換為源型。

並且我認為js裡判斷繼承(即A instanceof B)是沿著__proto__往深走的,A會通過自己本身的__proto__找到所指向的prototype判斷這個prototype是否是B的prototype,若屬於則直接返回true,若不屬於則沿著prototype的__proto__繼續往下找到一個新的prototype,再次判斷是否是B的prototype,最後若找到prototype為null則返回false(即試圖通過源型的__proto__找到下一個prototype,但源型的__proto__為null因此不可能再往下找了)

下面我來解釋下邊四個現象:

console.log(Function instanceof Function)
console.log(Function instanceof Object)
console.log(Object instanceof Function)
console.log(Object instanceof Object)

 

  1. Function的__proto__指向的prototype是Function本身的prototype,Function的prototype是屬於Function,因此返回true,
  2. Function的__proto__指向Function的prototype,再繼續往下找,Function的prototype的__proto__指向Object的prototype,Object的prototype是屬於Object的,因此返回true。
  3. Object的__proto__指向Function的prototype,Function的prototype是屬於Function,因此返回true。
  4. Object的__proto__指向Function的prototype,Function的prototype的__proto__指向Object的prototype,這個prototype是屬於Object,因此返回true。

網上說他們互相繼承我都搞亂了,但是理解其內部原理就非常容易解釋了。

下面再來一個小測試:

var obj = new Object;
obj.__proto__ = Function.prototype;
console.log(obj instanceof Function)

 

首先可以證明我上邊說的instanceof的判斷方法是沒錯的,但是直接修改__proto__來修改繼承關係是堅決不允許的,這裡僅僅是測試,要想看如何實現js類的繼承以及對prototype更深的解析看請我下篇博文。

最後給出本文和外界的介面,peototype是原型,__proto__所指向的以及其後的所有peototype稱為原型鏈。“js裡一切皆物件”倒不如所是js裡的所有物件都是由“源型”生成。