【進階5-3期】深入探究 Function & Object 雞蛋問題

木易楊說發表於2019-04-16

引言

上篇文章用圖解的方式向大家介紹了原型鏈及其繼承方案,在介紹原型鏈繼承的過程中講解原型鏈運作機制以及屬性遮蔽等知識,今天這篇文章就來深入探究下 Function.__proto__ === Function.prototype 引起的雞生蛋蛋生雞問題,並在這個過程中深入瞭解 Object.prototype、Function.prototype、function Object 、function Function 之間的關係。

Object.prototype

我們先來看看 ECMAScript 上的定義(15.2.4)。

The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is "Object", and the initial value of the [[Extensible]] internal property is true.

Object.prototype 表示 Object 的原型物件,其 [[Prototype]] 屬性是 null,訪問器屬性 __proto__ 暴露了一個物件的內部 [[Prototype]] 。 Object.prototype 並不是通過 Object 函式建立的,為什麼呢?看如下程式碼

function Foo() {
  this.value = 'foo';
}
let f = new Foo();
f.__proto__ === Foo.prototype;
// true
複製程式碼

例項物件的 __proto__ 指向建構函式的 prototype,即 f.__proto__ 指向 Foo.prototype,但是 Object.prototype.__proto__ 是 null,所以 Object.prototype 並不是通過 Object 函式建立的,那它如何生成的?其實 Object.prototype 是瀏覽器底層根據 ECMAScript 規範創造的一個物件。

Object.prototype 就是原型鏈的頂端(不考慮 null 的情況下),所有物件繼承了它的 toString 等方法和屬性。

【進階5-3期】深入探究 Function & Object 雞蛋問題

Function.prototype

我們先來看看 ECMAScript 上的定義(15.3.4)。

The Function prototype object is itself a Function object (its [[Class]] is "Function").

The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object.

The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object.

Function.prototype 物件是一個函式(物件),其 [[Prototype]] 內部屬性值指向內建物件 Object.prototype。Function.prototype 物件自身沒有 valueOf 屬性,其從 Object.prototype 物件繼承了valueOf 屬性。

【進階5-3期】深入探究 Function & Object 雞蛋問題

Function.prototype 的 [[Class]] 屬性是 Function,所以這是一個函式,但又不大一樣。為什麼這麼說呢?因為我們知道只有函式才有 prototype 屬性,但並不是所有函式都有這個屬性,因為 Function.prototype 這個函式就沒有。

Function.prototype
// ƒ () { [native code] }

Function.prototype.prototype
// undefined
複製程式碼

當然你會發現下面這個函式也沒有 prototype 屬性。

let fun = Function.prototype.bind()
// ƒ () { [native code] }

fun.prototype
// undefined
複製程式碼

為什麼沒有呢,我的理解是 Function.prototype 是引擎建立出來的函式,引擎認為不需要給這個函式物件新增 prototype 屬性,不然 Function.prototype.prototype… 將無休無止並且沒有存在的意義。

function Object

我們先來看看 ECMAScript 上的定義(15.2.3)。

The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object.

Object 作為建構函式時,其 [[Prototype]] 內部屬性值指向 Function.prototype,即

Object.__proto__ === Function.prototype
// true
複製程式碼
【進階5-3期】深入探究 Function & Object 雞蛋問題

使用 new Object() 建立新物件時,這個新物件的 [[Prototype]] 內部屬性指向建構函式的 prototype 屬性,對應上圖就是 Object.prototype。

【進階5-3期】深入探究 Function & Object 雞蛋問題

當然也可以通過物件字面量等方式建立物件。

  • 使用物件字面量建立的物件,其 [[Prototype]] 值是 Object.prototype
  • 使用陣列字面量建立的物件,其 [[Prototype]] 值是 Array.prototype
  • 使用 function f(){} 函式建立的物件,其 [[Prototype]] 值是 Function.prototype
  • 使用 new fun() 建立的物件,其中 fun 是由 JavaScript 提供的內建構造器函式之一(Object, Function, Array, Boolean, Date, Number, String 等等),其 [[Prototype]] 值是 fun.prototype。
  • 使用其他 JavaScript 構造器函式建立的物件,其 [[Prototype]] 值就是該構造器函式的 prototype 屬性。
let o = {a: 1};
// 原型鏈:	o ---> Object.prototype ---> null

let a = ["yo", "whadup", "?"];
// 原型鏈:	a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}
// 原型鏈:	f ---> Function.prototype ---> Object.prototype ---> null

let fun = new Function();
// 原型鏈:	fun ---> Function.prototype ---> Object.prototype ---> null

function Foo() {}
let foo = new Foo();
// 原型鏈:	foo ---> Foo.prototype ---> Object.prototype ---> null

function Foo() {
  return {};
}
let foo = new Foo();
// 原型鏈:	foo ---> Object.prototype ---> null
複製程式碼

function Function

我們先來看看 ECMAScript 上的定義(15.3.3)。

The Function constructor is itself a Function object and its [[Class]] is "Function". The value of the [[Prototype]] internal property of the Function constructor is the standard built-in Function prototype object.

Function 建構函式是一個函式物件,其 [[Class]] 屬性是 Function。Function 的 [[Prototype]] 屬性指向了 Function.prototype,即

Function.__proto__ === Function.prototype
// true
複製程式碼
【進階5-3期】深入探究 Function & Object 雞蛋問題

到這裡就有意思了,我們看下雞生蛋蛋生雞問題。

Function & Object 雞蛋問題

我們看下面這段程式碼

Object instanceof Function 		// true
Function instanceof Object 		// true

Object instanceof Object 			// true
Function instanceof Function 	// true
複製程式碼

Object 建構函式繼承了 Function.prototype,同時 Function 建構函式繼承了Object.prototype。這裡就產生了 雞和蛋 的問題。為什麼會出現這種問題,因為 Function.prototypeFunction.__proto__ 都指向 Function.prototype

// Object instanceof Function 	即
Object.__proto__ === Function.prototype 					// true

// Function instanceof Object 	即
Function.__proto__.__proto__ === Object.prototype	// true

// Object instanceof Object 		即 			
Object.__proto__.__proto__ === Object.prototype 	// true

// Function instanceof Function 即	
Function.__proto__ === Function.prototype					// true
複製程式碼

對於 Function.__proto__ === Function.prototype 這一現象有 2 種解釋,爭論點在於 Function 物件是不是由 Function 建構函式建立的一個例項?

解釋 1、YES:按照 JavaScript 中“例項”的定義,a 是 b 的例項即 a instanceof b 為 true,預設判斷條件就是 b.prototype 在 a 的原型鏈上。而 Function instanceof Function 為 true,本質上即 Object.getPrototypeOf(Function) === Function.prototype,正符合此定義。

解釋 2、NO:Function 是 built-in 的物件,也就是並不存在“Function物件由Function建構函式建立”這樣顯然會造成雞生蛋蛋生雞的問題。實際上,當你直接寫一個函式時(如 function f() {}x => x),也不存在呼叫 Function 構造器,只有在顯式呼叫 Function 構造器時(如 new Function('x', 'return x') )才有。

我個人偏向於第二種解釋,即先有 Function.prototype 然後有的 function Function() ,所以就不存在雞生蛋蛋生雞問題了,把 Function.__proto__ 指向 Function.prototype 是為了保證原型鏈的完整,讓 Function 可以獲取定義在 Object.prototype 上的方法。

最後給一個完整的圖,看懂這張圖原型就沒問題了。

jsobj

內建型別構建過程

JavaScript 內建型別是瀏覽器核心自帶的,瀏覽器底層對 JavaScript 的實現基於 C/C++,那麼瀏覽器在初始化 JavaScript 環境時都發生了什麼?

沒找到官方文件,下文參考自 segmentfault.com/a/119000000…

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

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

參考

從探究Function.__proto__===Function.prototype過程中的一些收穫

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

從__proto__和prototype來深入理解JS物件和原型鏈

文章穿梭機

進階系列目錄

  • 【進階1期】 呼叫堆疊
  • 【進階2期】 作用域閉包
  • 【進階3期】 this全面解析
  • 【進階4期】 深淺拷貝原理
  • 【進階5期】 原型Prototype
  • 【進階6期】 高階函式
  • 【進階7期】 事件機制
  • 【進階8期】 Event Loop原理
  • 【進階9期】 Promise原理
  • 【進階10期】Async/Await原理
  • 【進階11期】防抖/節流原理
  • 【進階12期】模組化詳解
  • 【進階13期】ES6重難點
  • 【進階14期】計算機網路概述
  • 【進階15期】瀏覽器渲染原理
  • 【進階16期】webpack配置
  • 【進階17期】webpack原理
  • 【進階18期】前端監控
  • 【進階19期】跨域和安全
  • 【進階20期】效能優化
  • 【進階21期】VirtualDom原理
  • 【進階22期】Diff演算法
  • 【進階23期】MVVM雙向繫結
  • 【進階24期】Vuex原理
  • 【進階25期】Redux原理
  • 【進階26期】路由原理
  • 【進階27期】VueRouter原始碼解析
  • 【進階28期】ReactRouter原始碼解析

交流

進階系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。

github.com/yygmind/blo…

我是木易楊,公眾號「高階前端進階」作者,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!

【進階5-3期】深入探究 Function & Object 雞蛋問題

相關文章