JS 究竟是先有雞還是有蛋,Object與Function究竟誰出現的更早,Function算不算Function的實例等問題雜談


壹 ❀ 引

我在JS 疫情宅在家,學習不能停,七千字長文助你徹底弄懂原型與原型鏈一文中介紹了JavaScript原型與原型鏈,以及衍生的__proto__、constructor等一系列屬性。在解答了多個問題的同時,也得出了很多有趣的結論。比如我們常說JavaScript中函數是一等公民,這是因為函數扮演了創造萬物的角色,原始構造函數Function創造了function fn(){}(ES5中函數與構造函數並無區別)Object()Array()Number()String()等諸多構造函數,而構造函數也擁有創造對應實例對象的能力,比如Array()生產數組,String()生產字符串,你會發現JavaScript中絕大多數的數據類型,都能找到創造自己的構造函數,所以說函數是一等公民不無道理。

但在之后的時間我發現這個結論存在部分誤導性以及問題,比如:

  • 如果說Function()扮演着創世主的角色,那Function.prototype不應該是僅次於原型鏈頂端null的存在嗎?
  • 在介紹原型鏈的過程中,我們知道緊接在null之下的是Object.prototypeObject.prototype的起源地位似乎比Function.prototype更早,那Object.__proto__ === Function.prototype又是怎么回事?Object與Function到底誰的起源更早,誰才是真正的創世主?
  • 我們說函數扮演着實例和構造器的雙重身份,這句話雖然沒錯,但Function.__proto__ === Function.prototype又是怎么回事?難道Function是Function的實例?自己誕生自己?

帶着這些問題,我們開始本篇文章的探討。

貳 ❀ Function()與Function.prototype

回到文章開頭,如果我們用圖解關系來表示Function是創世主的結論,應該這樣:

但是大家心里都清楚,Object()String()等構造器並不是真的由原始構造函數Function()創建,本質上來說,它們都由JavaScript底層實現。

我們之所以說出了這個結論,是因為這些構造器的__proto__均指向了構造器Function.prototype,取自上篇博客的代碼:

var fn = function () { };

Number.__proto__ === Function.prototype //true
Number.constructor === Function //true

String.__proto__ === Function.prototype //true
String.constructor === Function //true

Object.__proto__ === Function.prototype //true
Object.constructor === Function //true

fn.__proto__ === Function.prototype //true
fn.constructor === Function //true

讓我們把圖畫的更精准點,應該是這樣:

到這里不知道大家有沒有感覺到,Function()Function.prototype其實是兩個東西。我們得到的實際結論是,所有函數對象的__proto__都指向Function.prototype,包括Function()本身。

Function.__proto__ === Function.prototype //true
Function.constructor === Function //true

用圖表示就是這樣:

這句話至少能證明,Object()Number()甚至Function()這些構造器在誕生時,確實繼承了Function.prototype,至於底層在實現中時有沒有使用Function()那就不得而知。

所以單站在構造器的角度,從某種角度上說,Function.prototype確實是扮演了創世主的角色,所以說Function()是創世主這句話有一定誤導性,這里我做個糾正。

說了這么多,Function.prototype又是什么?我們嘗試打印:

console.log(typeof Function.prototype) // "function"

可以看到Function.prototype居然是一個函數!!!!在我印象中原型一直就是一個對象,實事如此,Function的原型就是特殊的存在。

var fn = function () {};
console.log(typeof fn.prototype) // "Object"

我們通過console.dir()展開Function.prototype這個函數的屬性,如下:

為什么所有的函數,原始構造器都可以使用call()、apply()這些方法呢?原來在Function.prototype這個函數上已經綁好了這些方法,而其它函數在創建時又繼承了Function.prototype這個函數,能用這些方法也是意料之內的事情了。

我們再來引用ECMAScript 15.3.4 上關於Function.prototype的概念加深印象:

The Function prototype object is itself a Function object (its [[Class]] is "Function") that, when invoked, accepts any arguments and returns undefined.

The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.

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

The length property of the Function prototype object is 0.

從官方說明中可以得知,Function.prototype確實是一個函數對象,它的__proto__屬性又指向Object.prototype。比如Function.prototype自身其實沒有valueOf方法,但它從Object原型上借用了這個方法。

我怕大家看到后面又忘記了前面的概念,這里我們畫成一個圖:

到這里我們又知道了並不是所有的prototype對象都是Object類型,比如Function.prototype就是一個函數。最重要的一點,Function()Function.prototype不是同一個東西,同理,Object()Object.prototype也不是同一個東西,一定要區分這兩者,這對於后面我們理解Object.prototype也有幫助。

叄 ❀ Object()與Object.prototype

通過前面的分析,我們知道Object()這個構造函數在創建時繼承了Function.prototype這個函數,怕你們弄混,上代碼:

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

Function.prototype.__proto__又指向了Object.prototype,上代碼:

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

有點繞,因為這是從官方說明中得到的信息,所以我們補充上面的圖:

那么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確確實實是一個對象,它的__proto__指向null,所以站在原型鏈的角度,Object.prototype的起源確實比Function.prototype要早。通過上面的關系圖就很清楚表示了這點。

//偽代碼
Object.prototype => Function.prototype => Object() => {}

還記得文章開頭提出的疑問中,我們說隨便創建一個函數fn,fn.prototype.__proto__指向Object.prototype,這是因為除了Function.prototype之外,其它對象的prototype都是Object類型,這里指向Object.prototype其實也不難理解。別忘了,我們說所有對象都有__proto__屬性,但函數除了有__protot__之外,還有prototype屬性,而一般函數的prototype都是對象類型,所以指向Object.prototype。避免大家混淆,我們直接上代碼:

var fn = function () {};
fn.__proto__ === Function.prototype;
fn.prototype.__proto__ === Object.prototype;

你看,fn__proto__屬性不是指向了創建自己的構造函數Function了嗎,前面我之所以提出疑問,就是將fn.prototypefn理解成了一個東西。

我們將這段理解變成圖,繼續補充我們之前的圖:

其實說到這,文章開頭提出的幾個疑惑,我心中都大致有了答案。首先扮演創世主的我更傾向於是函數Function.prototype,它創造了Function(),fn(),Number(),object()等一系列構造函數,如果說Function是Function的實例,我更傾向於Function是Function.prototype的實例,畢竟這些方法都繼承了Function.prototype上的方法屬性。當然這個理解可能有誤,我現在做的就是站在結論的角度倒推原因,只是這個原因我更容易理解。至於Object與Function誰更早出現,文中其中已經給了答案,真要說起源,對象Object.prototype確實更早一點,畢竟Function.prototype都是繼承於Object.prototype

除此之外,通過本文的分析,我們知道並不是所有的prototype都是對象類型,比如Function.prototype。並不是所有的函數都有prototype屬性,比如Function.prototype這個函數就沒有。

肆 ❀ 總

我成功抓住了二月了的尾巴,在最后一天又更新了一篇我想寫的文章。今天又是從中午十二點寫到了晚上七點(中間摸了會魚),哈哈哈,還是挺充實。這篇文章說到底我並不是太滿意,其實寫到最后,我解答了心中部分疑惑,也產生了新的疑惑,畢竟JavaScript不是咱們寫的,大部分結論都是靠自己推測,但既然寫到了這里,我也確實有了新的收獲,雖然對於編程幫助可能不大,但應付面試,我想各位和我一樣應該是綽綽有余了。

至於三月,我想花一部分時間研究小程序,去年腦抽寫了一篇入門教程,居然還有不錯的點擊量,那么還是給自己定個目標,繼續更新下去吧。

JavaScript知識的進階學習還會繼續,各位,一起努力吧。

另外,關於文中原型的圖解,還是請參考下面這張好點,文中的圖只是為了自己理清思路,一旦懂了這些知識,下面這張圖就是日后復習不錯的提示點。

參考

深入探究 Function & Object 雞蛋問題

從探究Function.proto===Function.prototype過程中的一些收獲

詳解prototype與__proto__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM