之前分享了一篇文章JS原型鏈與繼承別再被問倒了,發現繼承的問題研究到後面,一定會觸及到Object.prototype和Function.prototype這些概念,為了解答疑惑,本篇就抽絲剝繭,從prototype與__proto__來推出函式與物件的深層關係。
原文:詳解prototype與__proto__
概念
- prototype 是函式(function) 的一個屬性, 它指向函式的原型.
- __proto__ 是物件的內部屬性, 它指向構造器的原型, 物件依賴它進行原型鏈查詢,instanceof 也是依賴它來判斷是否繼承關係.
由上, prototype 只有函式才有, 其他(非函式)物件不具有該屬性. 而 __proto__ 是物件的內部屬性, 任何物件都擁有該屬性.
栗子
下面我們來吃個栗子幫助消化下:
function Person(name){
this.name = name;
}
var p1 = new Person('louis');
console.log(Person.prototype);//Person原型 {constructor: Person(name),__proto__: Object}
console.log(p1.prototype);//undefined
console.log(Person.__proto__);//空函式, function(){}
console.log(p1.__proto__ == Person.prototype);//true複製程式碼
吃栗子時我們發現, Person.prototype(原型) 預設擁有兩個屬性:
- constructor 屬性, 指向構造器, 即Person本身
- __proto__ 屬性, 指向一個空的Object 物件
而p1作為非函式物件, 自然就沒有 prototype 屬性; 此處佐證了概念1
下面來看看__proto__屬性:
Person.__proto__ 屬性 指向的是一個空函式( function(){} ), 待會兒我們再來研究這個空函式.
p1.__proto__ 屬性 指向的是 構造器(Person) 的原型, 即 Person.prototype. 此處佐證了概念2
這裡我們發現: 原型鏈查詢時, 正是通過這個屬性(__proto__) 連結到構造器的原型, 從而實現查詢的層層深入.
對 概念1
不太理解的同學, 說明你們不會吃栗子, 我們們忽略他們. 對 概念2
不太理解的同學, 我們來多吃幾個栗子, 邊吃邊想:
var obj = {name: 'jack'},
arr = [1,2,3],
reg = /hello/g,
date = new Date,
err = new Error('exception');
console.log(obj.__proto__ === Object.prototype); // true
console.log(arr.__proto__ === Array.prototype); // true
console.log(reg.__proto__ === RegExp.prototype); // true
console.log(date.__proto__ === Date.prototype); // true
console.log(err.__proto__ === Error.prototype); // true複製程式碼
可見, 以上通過 物件字面量
和 new + JS引擎內建構造器() 建立的物件, 無一例外, 它們的__proto__ 屬性全部指向構造器的原型(prototype). 充分佐證了 概念2
.
__proto__
剛才留下了一個問題: Person.__proto__ 指向的是一個空函式, 下面我們來看看這個空函式究竟是什麼.
console.log(Person.__proto__ === Function.prototype);//true複製程式碼
Person 是構造器也是函式(function), Person的__proto__ 屬性自然就指向 函式(function)的原型, 即 Function.prototype.
這說明了什麼呢?
我們由 "特殊" 聯想到 "通用" , 由Person構造器聯想一般的構造器.
這說明 所有的構造器都繼承於Function.prototype (此處我們只是由特殊總結出了普適規律, 並沒有給出證明, 請耐心看到後面) , 甚至包括根構造器Object及Function自身。所有構造器都繼承了Function.prototype的屬性及方法。如length、call、apply、bind(ES5)等. 如下:
console.log(Number.__proto__ === Function.prototype); // true
console.log(Boolean.__proto__ === Function.prototype); // true
console.log(String.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
console.log(RegExp.__proto__ === Function.prototype); // true
console.log(Error.__proto__ === Function.prototype); // true
console.log(Date.__proto__ === Function.prototype); // true複製程式碼
JavaScript中有內建(build-in)構造器/物件共計13個(ES5中新加了JSON),這裡列舉了可訪問的9個構造器。剩下如Global不能直接訪問,Arguments僅在函式呼叫時由JS引擎建立,Math,JSON是以物件形式存在的,無需new。由於任何物件都擁有 __proto__ 屬性指向構造器的原型. 即它們的 __proto__ 指向Object物件的原型(Object.prototype)。如下所示:
console.log(Math.__proto__ === Object.prototype); // true
console.log(JSON.__proto__ === Object.prototype); // true複製程式碼
如上所述, 既然所有的構造器都來自於Function.prototype, 那麼Function.prototype 到底是什麼呢?
Function.prototype
我們借用 typeof 運算子來看看它的型別.
console.log(typeof Function.prototype) // "function"複製程式碼
實際上, Function.prototype也是唯一一個typeof XXX.prototype為 “function”的prototype。其它的構造器的prototype都是一個物件。如下:
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Object.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object複製程式碼
JS中函式是一等公民
既然Function.prototype 的型別是函式, 那麼它會擁有 __proto__ 屬性嗎, Function.prototype.__proto__ 會指向哪裡呢? 會指向物件的原型嗎? 請看下方:
console.log(Function.prototype.__proto__ === Object.prototype) // true複製程式碼
透過上方程式碼, 且我們瞭解到: Function.prototype 的型別是函式, 也就意味著一個函式擁有 __proto__ 屬性, 並且該屬性指向了物件(Object)構造器的原型. 這意味著啥?
根據我們在 概念2
中瞭解到的: __proto__ 是物件的內部屬性, 它指向構造器的原型.
這意味著 Function.prototype 函式 擁有了一個物件的內部屬性, 並且該屬性還恰好指向物件構造器的原型. 它是一個物件嗎? 是的, 它一定是物件. 它必須是.
實際上, JavaScript的世界觀裡, 函式也是物件, 函式是一等公民.
這說明所有的構造器既是函式
也是一個普通JS物件
,可以給構造器新增/刪除屬性等。同時它也繼承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。
Object.prototype
函式的 __proto__ 屬性指向 Function.prototype, 如: Person.__proto__ —> Function.prototype
Function.prototype 函式的 __proto__ 屬性指向 Object.prototype, 如: Function.prototype.__proto__ —> Object.prototype.
那麼Object.prototype.__proto__ 指向什麼呢?
console.log(Object.prototype.__proto__ === null);//true複製程式碼
由於已經到頂, JS世界的源頭一片荒蕪, 竟然什麼都沒有! 令人嗟嘆不已.
都說一圖勝千言, 我也不能免俗. 下面附一張 stackoverflow 上的圖:
這張圖也指出:
- Object.__proto__ == Function.prototype,
- Function.__proto__ == Function.prototype.
//雖然上面做過測試, 我們還是再次測試下
console.log(Object.__proto__ == Function.prototype);//true
console.log(Function.__proto__ == Function.prototype);//true複製程式碼
由於物件構造器 (Object) 也是構造器, 又構造器都是函式, 又函式是一等公民, 函式也是物件.
故, 物件構造器 (Object) 擁有3種身份:
構造器
函式
物件
推而廣之, 所有構造器都擁有上述3種身份.
由於構造器是 物件
(身份3), 理所當然擁有 __proto__ 屬性, 且該屬性一定指向其構造器的原型, 也就是指向 函式
(身份2) 構造器(Function)的原型, 即 Function.prototype. 於是我們證明了上面那句 所有的構造器都繼承於Function.prototype (身份1).
注: 上面程式碼中用到的 __proto__ 目前在IE6/7/8/9中並不支援。IE9中可以使用Object.getPrototypeOf(ES5)獲取物件的內部原型。
附上網友的疑問(問題提得特別好,問出了函式與物件最尖銳的歸宿問題):
問題背景:先有 Object.prototype(原型鏈頂端),Function.prototype 繼承 Object.prototype 而產生,最後,Function 和 Object 和其它建構函式繼承 Function.prototype 而產生。
以下是具體問題:
先有 Object.prototype,再有 Object,那麼先有 Object.prototype 裡面的這個 Object 代表的是什麼呢?
Function.__proto__=== Function.prototype
;
Function 建構函式的 prototype 屬性和__proto__屬性都指向同一個原型,是否可以說 Function 物件是由 Function 建構函式建立的一個例項?Object instanceof Function
// trueFunction instanceof Object
// true
Object 本身是建構函式,繼承了 Function.prototype; Function 也是物件,繼承了Object.prototype。感覺成了雞和蛋的問題了。比如說:
function Person(){}
Person.prototype 是一個物件,為什麼 Function.prototype 卻是一個函式呢,當然函式也是一個物件,為什麼要這麼設計呢?
這裡是疑惑:
感覺有些地方很難理解,總感覺有種悖論一樣,還有人扯到羅素悖論,各有說辭,不知道樓主怎麼看。。。
通讀全文如果你能回答這些問題,說明看懂了本文。否則請允許我建議你再讀一篇,別打我。
---華麗麗的分割線---
以下是回答:
答1: 先有的一定是Object,它是BOM物件,列印出來為:function Object() { [native code] }
,然後才是Object.prototype。這不矛盾,請看分析。
首先:Object.create(null)你應該知道,它可以建立一個沒有原型的物件。如下:Object.prototype.__proto__ == Object.create(null).__proto__
結果為true,也就是說,Object.prototype是使用這種方式生成的,然後才綁在Object的prototype屬性上。為什麼這裡沒有用===
,因為__proto__指向的是物件,物件是沒法比較相等的。
要知道,物件可以先生成,然後再賦予新的屬性。
答2: Function.__proto__=== Function.prototype
這沒毛病。
我們都知道,Function是一個函式,只要是函式,它的__proto__就指向 Funtion.prototype. 這是繼承的設定。那麼我們怎麼理解這種構造器本身就繼承本身的prototype屬性的現象呢?
Function生成時,是沒有__proto__屬性的,它是一個BOM物件,列印出來為:function Function() { [native code] }
,Function.prototype同樣是BOM物件,列印出來為:function () { [native code] }
,那麼可以這麼理解:
Function的__proto__和prototype屬性都是後面才指向同一個BOM物件的。
答3: 這不是蛋和雞的問題,計算機中沒有蛋和雞。Object instanceof Function
// true 。Object是構造器函式,你必須認,有new Object()
為證。
答2已經給了結論:只要是函式,它的__proto__就指向 Funtion.prototype。Object也是函式。因此它的__proto__屬性會指向Funtion.prototype。故而Object instanceof Function 為 true。
答4: Function.prototype是一個函式,也是物件。這是自然的。
那麼為什麼這麼設計呢?你注意到沒有 typeof Function.prototype === "function"
結果為true。注意:只有Function的prototype是函式,其他都是普通物件。它是一個橋樑,我們一直說函式也是物件,只有這個滿足了, 函式才能是物件,關於『js中函式是一等公民』的定理,Function.prototype同時是函式, 又是原型物件便是佐證。
說了說去,都回到了Function.prototype、Function和Object的問題上,本質上它們都是BOM物件,即它們都在瀏覽器global物件(這個js是訪問不到的)生成之前生成的,後面的指向關係,可以認為是js世界繼承規律的一種設定,有了這個設定,js遊戲才能玩下去。
本文就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論.
本文作者: louis
本文連結: louiszhai.github.io/2015/12/17/…
本次活動連結:juejin.im/post/58d8e9…
參考文章