<input id="0qass"><u id="0qass"></u></input>
  • <input id="0qass"><u id="0qass"></u></input>
  • <menu id="0qass"><u id="0qass"></u></menu>

    幫你徹底搞懂JS中的prototype、__proto__與constructor(圖解)

    提示:不要排斥,靜下心來,認真讀完,你就搞懂了!(可以先看一下最后的總結部分再回過頭來完整看完)

    1. 前言

    ??作為一名前端工程師,必須搞懂JS中的prototype、__proto__constructor屬性,相信很多初學者對這些屬性存在許多困惑,容易把它們混淆,本文旨在幫助大家理清它們之間的關系并徹底搞懂它們。這里說明一點,__proto__屬性的兩邊是各由兩個下劃線構成(這里為了方便大家看清,在兩下劃線之間加入了一個空格:_ _proto_ _,讀作“dunder proto”,“double underscore proto”的縮寫),實際上,該屬性在ES標準定義中的名字應該是[[Prototype]],具體實現是由瀏覽器代理自己實現,谷歌瀏覽器的實現就是將[[Prototype]]命名為__proto__,大家清楚這個標準定義與具體實現的區別即可(名字有所差異,功能是一樣的),可以通過該方式檢測引擎是否支持這個屬性:Object.getPrototypeOf({__proto__: null}) === null。本文基于谷歌瀏覽器(版本 72.0.3626.121)的實驗結果所得。
    ?? 現在正式開始! 讓我們從如下一個簡單的例子展開討論,并配以相關的圖幫助理解:

    function Foo() {...};
    let f1 = new Foo();
    

    以上代碼表示創建一個構造函數Foo(),并用new關鍵字實例化該構造函數得到一個實例化對象f1。這里稍微補充一下new操作符將函數作為構造器進行調用時的過程:函數被調用,然后新創建一個對象,并且成了函數的上下文(也就是此時函數內部的this是指向該新創建的對象,這意味著我們可以在構造器函數內部通過this參數初始化值),最后返回該新對象的引用,詳細請看:詳解JavaScript中的new操作符。雖然是簡簡單單的兩行代碼,然而它們背后的關系卻是錯綜復雜的,如下圖所示:
    整體的聯系看到這圖別怕,讓我們一步步剖析,徹底搞懂它們!
    ??圖的說明:右下角為圖例,紅色箭頭表示__proto__屬性指向、綠色箭頭表示prototype屬性的指向、棕色實線箭頭表示本身具有的constructor屬性的指向,棕色虛線箭頭表示繼承而來的constructor屬性的指向;藍色方塊表示對象,淺綠色方塊表示函數(這里為了更好看清,Foo()僅代表是函數,并不是指執行函數Foo后得到的結果,圖中的其他函數同理)。圖的中間部分即為它們之間的聯系,圖的最左邊即為例子代碼。

    2. _ _ proto _ _ 屬性

    ??首先,我們需要牢記兩點:①__proto__constructor屬性是對象所獨有的;② prototype屬性是函數所獨有的。但是由于JS中函數也是一種對象,所以函數也擁有__proto__constructor屬性,這點是致使我們產生困惑的很大原因之一。上圖有點復雜,我們把它按照屬性分別拆開,然后進行分析:
    __proto__
    ??第一,這里我們僅留下 __proto__ 屬性,它是對象所獨有的,可以看到__proto__屬性都是由一個對象指向一個對象,即指向它們的原型對象(也可以理解為父對象),那么這個屬性的作用是什么呢?它的作用就是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那么就會去它的__proto__屬性所指向的那個對象(可以理解為父對象)里找,如果父對象也不存在這個屬性,則繼續往父對象的__proto__屬性所指向的那個對象(可以理解為爺爺對象)里找,如果還沒找到,則繼續往上找…直到原型鏈頂端null(可以理解為原始人。。。),再往上找就相當于在null上取值,會報錯(可以理解為,再往上就已經不是“人”的范疇了,找不到了,到此結束,null為原型鏈的終點),由以上這種通過__proto__屬性來連接對象直到null的一條鏈即為我們所謂的原型鏈。
    ??其實我們平時調用的字符串方法、數組方法、對象方法、函數方法等都是靠__proto__繼承而來的。

    3. prototype屬性

    ??第二,接下來我們看 prototype 屬性:
    prototype屬性??prototype屬性,別忘了一點,就是我們前面提到要牢記的兩點中的第二點,它是函數所獨有的,它是從一個函數指向一個對象。它的含義是函數的原型對象,也就是這個函數(其實所有函數都可以作為構造函數)所創建的實例的原型對象,由此可知:f1.__proto__ === Foo.prototype,它們兩個完全一樣。那prototype屬性的作用又是什么呢?它的作用就是包含可以由特定類型的所有實例共享的屬性和方法,也就是讓該函數所實例化的對象們都可以找到公用的屬性和方法。任何函數在創建的時候,其實會默認同時創建該函數的prototype對象。

    4. constructor屬性

    ??最后,我們來看一下 constructor 屬性:
    constructor屬性??constructor屬性也是對象才擁有的,它是從一個對象指向一個函數,含義就是指向該對象的構造函數,每個對象都有構造函數(本身擁有或繼承而來,繼承而來的要結合__proto__屬性查看會更清楚點,如下圖所示),從上圖中可以看出Function這個對象比較特殊,它的構造函數就是它自己(因為Function可以看成是一個函數,也可以是一個對象),所有函數和對象最終都是由Function構造函數得來,所以constructor屬性的終點就是Function這個函數。
    constructor繼承
    ??感謝網友的指出,這里解釋一下上段中“每個對象都有構造函數”這句話。這里的意思是每個對象都可以找到其對應的constructor,因為創建對象的前提是需要有constructor,而這個constructor可能是對象自己本身顯式定義的或者通過__proto__在原型鏈中找到的。而單從constructor這個屬性來講,只有prototype對象才有。每個函數在創建的時候,JS會同時創建一個該函數對應的prototype對象,而函數創建的對象.__proto__ === 該函數.prototype,該函數.prototype.constructor===該函數本身,故通過函數創建的對象即使自己沒有constructor屬性,它也能通過__proto__找到對應的constructor,所以任何對象最終都可以找到其構造函數(null如果當成對象的話,將null除外)。如下:
    constructor說明

    5. 總結

    ?? 總結一下:

    1. 我們需要牢記兩點:①__proto__constructor屬性是對象所獨有的;② prototype屬性是函數所獨有的,因為函數也是一種對象,所以函數也擁有__proto__constructor屬性。
    2. __proto__屬性的作用就是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那么就會去它的__proto__屬性所指向的那個對象(父對象)里找,一直找,直到__proto__屬性的終點null,再往上找就相當于在null上取值,會報錯。通過__proto__屬性將對象連接起來的這條鏈路即我們所謂的原型鏈。
    3. prototype屬性的作用就是讓該函數所實例化的對象們都可以找到公用的屬性和方法,即f1.__proto__ === Foo.prototype。
    4. constructor屬性的含義就是指向該對象的構造函數,所有函數(此時看成對象了)最終的構造函數都指向Function。

    ??本文就此結束了,希望對那些對JS中的prototype、__proto__constructor屬性有困惑的同學有所幫助。

    最后,感謝這兩篇博文,本文中的部分內容參考自這兩篇博文:

    若對你有幫助,可以支持一下作者創作更多好文章哦,一分錢也是愛~
    贊賞碼

    已標記關鍵詞 清除標記
    <div><p>就標題而言,這是七八篇里起得最滿意的,高大上,即使外行人也會不明覺厲! :joy:</p> <p>不過不是開玩笑,本文的確打算從<code>__proto__</code>和<code>prototype</code>這兩個容易混淆來理解JS的終極命題之一:<strong>對象與原型鏈</strong>。</p> <h3><code>__proto__</code>和<code>prototype</code></h3> <h4><code>__proto__</code></h4> <p>引用《JavaScript權威指南》的一段描述:</p> <p>Every JavaScript object has a second JavaScript object (or null , but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.</p> <p>翻譯出來就是每個JS對象一定對應一個原型對象,并從原型對象繼承屬性和方法。好啦,既然有這么一個原型對象,那么對象怎么和它對應的?</p> <p><strong>對象<code>__proto__</code>屬性的值就是它所對應的原型對象:</strong></p> <pre><code> js var one = {x: 1}; var two = new Object(); one.__proto__ === Object.prototype // true two.__proto__ === Object.prototype // true one.toString === one.__proto__.toString // true </code></pre> <p>上面的代碼應該已經足夠解釋清楚<code>__proto__</code>了:grin:。好吧,顯然還不夠,或者說帶來了新的問題:<code>Object.prototype</code>是什么?憑什么說<code>one</code>和<code>two</code>的原型就是<code>Object.prototype</code>?</p> <h4><code>prototype</code></h4> <p>首先來說說<code>prototype</code>屬性,不像每個對象都有<code>__proto__</code>屬性來標識自己所繼承的原型,只有函數才有<code>prototype</code>屬性。</p> <p>為什么只有函數才有<code>prototype</code>屬性?ES規范就這么定的。</p> <p>開玩笑了,其實函數在JS中真的很特殊,是所謂的_一等公民_。JS不像其它面向對象的語言,它沒有類(<code>class</code>,ES6引進了這個關鍵字,但更多是語法糖)的概念。JS通過函數來模擬類。</p> <p>當你創建函數時,JS會為這個函數自動添加<code>prototype</code>屬性,~~值是空對象~~ <strong>值是一個有 constructor 屬性的對象,不是空對象</strong>。而一旦你把這個函數當作構造函數(<code>constructor</code>)調用(即通過<code>new</code>關鍵字調用),那么JS就會你創建該構造函數的實例,實例繼承構造函數<code>prototype</code>的所有屬性和方法(實例通過設置自己的<code>__proto__</code>指向承構造函數的<code>prototype</code>來實現這種繼承)。</p> <h4>小結</h4> <p>雖然對不熟悉的人來說還有點繞,但JS正是通過<code>__proto__</code>和<code>prototype</code>的合作實現了原型鏈,以及對象的繼承。</p> <p>構造函數,通過<code>prototype</code>來存儲要共享的屬性和方法,也可以設置<code>prototype</code>指向現存的對象來繼承該對象。</p> <p>對象的<code>__proto__</code>指向自己構造函數的<code>prototype</code>。<code>obj.__proto__.__proto__...</code>的原型鏈由此產生,包括我們的操作符<code>instanceof</code>正是通過探測<code>obj.__proto__.__proto__... === Constructor.prototype</code>來驗證<code>obj</code>是否是<code>Constructor</code>的實例。</p> <p>回到開頭的代碼,<code>two = new Object()</code>中<code>Object</code>是構造函數,所以<code>two.__proto__</code>就是<code>Object.prototype</code>。至于<code>one</code>,ES規范定義對象字面量的原型就是<code>Object.prototype</code>。</p> <h3>更深一步的探討</h3> <p>我們知道JS是單繼承的,<code>Object.prototype</code>是原型鏈的頂端,所有對象從它繼承了包括<code>toString</code>等等方法和屬性。</p> <p><code>Object</code>本身是構造函數,繼承了<code>Function.prototype</code>;<code>Function</code>也是對象,繼承了<code>Object.prototype</code>。這里就有一個_雞和蛋_的問題:</p> <pre><code> js Object instanceof Function // true Function instanceof Object // true </code></pre> <p>什么情況下會出現雞和蛋的問題呢?<a href="http://zhi.hu/o3gl">就是聲明一個包含所有集合的集合??!好了,你們知道這是羅素悖論,但并不妨礙PL中這樣設計。</a></p> <p>那么具體到JS,ES規范是怎么說的?</p> <p>Function<strong>本身就是函數</strong>,<code>Function.__proto__</code>是標準的內置對象<code>Function.prototype</code>。</p> <p><code>Function.prototype.__proto__</code>是標準的內置對象<code>Object.prototype</code>。</p> <p>以上均翻譯自http://www.ecma-international.org/ecma-262/5.1/#sec-15,_雞和蛋_的問題就是這么出現和設計的:<strong><code>Function</code>繼承<code>Function</code>本身,<code>Function.prototype</code>繼承<code>Object.prototype</code>。</strong></p> <h4>一張圖和總結</h4> <p><img alt="原型鏈" src="http://7sbnba.com1.z0.glb.clouddn.com/github-js-prototype.jpg" /></p> <p><em>Update: 圖片來自 <a href="http://www.mollypages.org/tutorials/js.mp">mollypages.org</a></em></p> <p>相信經過上面的詳細闡述,這張圖應該一目了然了。 1. <code>Function.prototype</code>和<code>Function.__proto__</code>都指向<code>Function.prototype</code>,這就是雞和蛋的問題怎么出現的。 2. <code>Object.prototype.__proto__ === null</code>,說明原型鏈到<code>Object.prototype</code>終止。</p><p>該提問來源于開源項目:creeperyang/blog</p></div>
    ??2020 CSDN 皮膚主題: 代碼科技 設計師:Amelia_0503 返回首頁
    多乐彩