從prototype與__proto__窺探JS繼承之源 | 掘金技術徵文

以前分享了一篇文章JS原型鏈與繼承別再被問倒了,發現繼承的問題研究到後面,必定會觸及到Object.prototype和Function.prototype這些概念,爲了解答疑惑,本篇就抽絲剝繭,從prototype與__proto__來推出函數與對象的深層關係。
原文:詳解prototype與__proto__html

概念

  1. prototype 是函數(function) 的一個屬性, 它指向函數的原型.
  2. __proto__ 是對象的內部屬性, 它指向構造器的原型, 對象依賴它進行原型鏈查詢,instanceof 也是依賴它來判斷是否繼承關係.

由上, prototype 只有函數纔有, 其餘(非函數)對象不具備該屬性. 而 __proto__ 是對象的內部屬性, 任何對象都擁有該屬性. git

栗子

下面咱們來吃個栗子幫助消化下:github

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 屬性; 此處佐證了概念1app

下面來看看__proto__屬性:函數

Person.__proto__ 屬性 指向的是一個空函數( function(){} ), 待會兒咱們再來研究這個空函數.post

p1.__proto__ 屬性 指向的是 構造器(Person) 的原型, 即 Person.prototype. 此處佐證了概念2測試

這裏咱們發現: 原型鏈查詢時, 正是經過這個屬性(__proto__) 連接到構造器的原型, 從而實現查詢的層層深刻.ui

概念1 不太理解的同窗, 說明大家不會吃栗子, 我們忽略他們. 對 概念2 不太理解的同窗, 咱們來多吃幾個栗子, 邊吃邊想:this

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 而產生。

如下是具體問題:

  1. 先有 Object.prototype,再有 Object,那麼先有 Object.prototype 裏面的這個 Object 表明的是什麼呢?

  2. Function.__proto__=== Function.prototype;
    Function 構造函數的 prototype 屬性和__proto__屬性都指向同一個原型,是否能夠說 Function 對象是由 Function 構造函數建立的一個實例?

  3. Object instanceof Function // true
    Function instanceof Object // true
    Object 自己是構造函數,繼承了 Function.prototype; Function 也是對象,繼承了Object.prototype。感受成了雞和蛋的問題了。

  4. 好比說:
    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…

參考文章

相關文章
相關標籤/搜索