函式物件、物件、原型

路人甲1024發表於2019-03-09

序、範例程式碼

以下為範例程式碼,本文中講解多次用到這段程式碼,可以在閱讀本文前提前執行,邊看文邊敲碼驗證。

function Person(){}; //建構函式
var person = new Person(); //例項物件複製程式碼

一、物件

每個物件都是基於一個引用型別建立的,這個引用型別可以是原生型別(Object、Array、Function等),也可以是使用者自定義的型別。

物件總的來說大致可以分成兩種:

1、函式物件(Function Object)

通過new Function()生成函式併為其指定函式名,通過函式名來進行呼叫,其實就是我們通常所說的函式。

2、其他型別的物件(Object)

直接通過new操作符生成(除了Function型別)的物件,如new Object(); 。

物件通過new操作符建立的過程:

(1)建立一個新物件;

(2)將建構函式的作用域賦給新物件(this就會指向這個新物件);

(3)執行建構函式中的程式碼(為物件新增屬性);

(4)返回新物件。

二、原型指標(__proto__)

當建構函式建立一個新例項(又稱例項物件,下同)後,該例項下都會存在一個指標__proto__(內部屬性),指向建立這個例項的建構函式(constructor指向值)下的原型物件(prototype)。只要是個物件,都會有__proto__。

根據範例程式碼來看,person是屬於建構函式Person的一個例項,person.__proto__ === Person.prototype為true。要明確的真正重要的一點就是,這個連線存在於例項與建構函式的原型物件之間,而不是存在於例項與建構函式之間。

這裡出現了另外兩個名詞:建構函式和原型物件,請看下面內容。

三、建構函式(constructor指向值)

建構函式的解釋是這樣的,建立物件時初始化物件。任何函式,只要通過new操作符來呼叫,那它就可以作為建構函式。像Object和Array等這樣執行環境自帶的稱為原生建構函式,它們會在執行時自動出現在執行環境中。

觀察範例程式碼,person是一個物件,而他的建構函式就是Person,Person.prototype.constructor === Person為true。

四、原型物件(prototype)

原型物件比較特殊(劃重點),只有函式物件會擁有,是用來解決建構函式在建立例項的時候,防止重複執行所導致的效能的降低(這裡主要指佔用記憶體),為複用帶來方便。

無論什麼時候,只要建立了一個新函式,就會根據一組特定的規則為該函式建立一個prototype屬性,這個屬性是一個指標,指向函式的原型物件,這個原型物件的用途是包含可以由特定型別的所有例項共享的屬性和方法,如果按照字面意思來理解,那麼prototype也是通過呼叫建構函式而建立的那個物件例項的原型物件(__proto__指向值)。

在預設情況下,所有原型物件(prototype)都會自動獲得一個constructor屬性,這個屬性包含一個指標,指向prototype屬性所在函式,通常會是建構函式。

五、原型搜尋機制

每當程式碼讀取某個物件的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性。搜尋首先從物件例項本身開始。如果在例項中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜尋指標指向的原型物件,在原型物件中查詢具有給定名字的屬性。如果在原型物件中找到了這個屬性,則返回該屬性的值。

以上就是原型搜尋機制的大致介紹。原型鏈繼承(此篇不講,有興趣自行谷歌百度)則是在這個基礎上進行擴充套件,然後實現繼承,基本思想是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。

六、原型關係圖

函式物件、物件、原型

如上圖,我們從最簡單的關係入手。以範例程式碼為例,建立Person函式,然後進行例項化生成例項物件person。

我們來看看例項物件person和函式Person各自的原型關係:

1、person(例項物件)

person會有一個__proto__指標,指向建構函式的原型物件Person.prototype。Person.prototype會有兩個屬性constructor和__proto__,constructor指向建構函式Person,__proto__指向原生型別Object的原型物件Object.prototype。Object.prototype有兩個屬性constructor和__proto__,constructor指向它的建構函式Object,而比較特殊的是__proto__的值為null(原型鏈的終點),用下列程式碼可驗證。

person.__proto__ === Person.prototype; //true
person.__proto__.__proto__ === Person.prototype.__proto__; //true
person.__proto__.__proto__ === Object.prototype; //true
Person.prototype.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true複製程式碼

2、Person(建構函式)

Person函式也會有一個__proto__指標,不同的是多了一個prototype物件屬性。prototype的構成上面已經介紹過就不重複了,__proto__指向原生型別Function的原型物件Function.prototype。Function.prototype有兩個屬性constructor和__proto__,constructor指向建構函式Function,__proto__指向Object的原型物件Object.prototype,介紹同上。

Person.__proto__ === Function.prototype; //true
Function.prototype.__proto__ === Object.prototype; //true
Person.prototype.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true複製程式碼

3、原生型別函式

Function函式和Object函式除了prototype之外都存在一個__proto__指標,都是指向Function的原型物件Function.prototype,介紹同上。

Object.__proto__ === Function.prototype; //true
Function.__proto__ === Function.prototype; //true複製程式碼

4、總結

建構函式、原型和例項的關係:每個建構函式都有一個原型物件,原型物件都包含一個指向建構函式的指標,而例項都包含一個指向原型的內部指標。所有建構函式的__proto__指標指向都是Function.prototype;所有物件的__proto__指標最終指向都是指向Object.prototype,而Object.prototype的__proto__屬性值則為null。

七、寫在最後

最近在網上看見一個類似關於Function和Object誰是爸爸(由誰而來)的問題,記得最開始學js時就把“js中萬物皆物件”這句話給記牢了,想著應該是物件最原始,但是凡事都要有個理(拿出證據來),所以就深究了一下以上函式和物件的一些關係,至於誰是爸爸,明確不出來,但是有一點可以肯定的是,無論是誰,按關係圖最後的走向都是null(貌似已經出現結果。。。)。

八、參考文獻

最詳盡的 JS 原型與原型鏈終極詳解

徹底理解JavaScript原型鏈(一)—__proto__的預設指向

高能!typeof Function.prototype 引發的先有 Function 還是先有 Object 的探討

《javascript高階程式設計》

附錄一:較為完整的原型鏈關係圖

image.png

附錄二:瀏覽器在初始化JS 環境時都發生了些什麼(猜的)

1.用 C/C++ 構造內部資料結構建立一個 OP 即(Object.prototype)以及初始化其內部屬性但不包括行為。

2.用 C/C++ 構造內部資料結構建立一個 FP 即(Function.prototype)以及初始化其內部屬性但不包括行為。

3.將 FP 的[[Prototype]]指向 OP。

4.用 C/C++ 構造內部資料結構建立各種內建引用型別。

5.將各內建引用型別的[[Prototype]]指向 FP。

6.將 Function 的 prototype 指向 FP。

7.將 Object 的 prototype 指向 OP。

8.用 Function 例項化出 OP,FP,以及 Object 的行為並掛載。

9.用 Object 例項化出除 Object 以及 Function 的其他內建引用型別的 prototype 屬性物件。

10.用 Function 例項化出除Object 以及 Function 的其他內建引用型別的 prototype 屬性物件的行為並掛載。

11.例項化內建物件 Math 以及 Grobal

至此,所有 內建型別構建完成。

原創不容易,轉載請宣告出處,謝謝!


相關文章