淺談JavaScript原型

前言

首先咱們須要明確兩點:git

1️⃣__proto__constructor對象獨有的github

2️⃣prototype屬性是函數獨有的瀏覽器

原型

prototype

  • 在最新ES規範裏,prototype 被定義爲:給其它對象提供共享屬性的對象。
  • 也就是說,prototype 本身也是對象,只是被用以承擔某個職能罷了。

所以,prototype 描述的是兩個對象之間的某種關係(其中一個,爲另外一個提供屬性訪問權限)。bash

constructor與prototype聯繫

  • 每一個函數都有一個prototype屬性,它默認指向一個Object空對象(即稱爲:原型對象)
  • 原型對象中有一個屬性constructor,它指向函數對象
  • 給原型對象添加屬性(通常是方法)
    • 做用:函數的全部實例對象自動擁有原型中的屬性(方法)

下面經過一個例子來講明:函數

function Demo() {}
        console.log(Demo.prototype.constructor === Demo)  // true
        console.dir(Demo.prototype)
複製代碼

能夠看到就是Demo函數對象的prototype原型是右邊這個對象,那麼Demo.prototype原型上有個constructor屬性,這個屬性正好指向Demo函數自己。post

全部你能夠理解成:性能

A的顯示原型是B,則有:
A.prototype === B 
B.constructor === A
複製代碼

我以爲這樣子惟一的好處在於你能夠找到我,我也能夠找到你。好滑稽ui

__proto__和prototype關係

再次強調 :this

1️⃣__proto__constructor對象獨有的。2️⃣prototype屬性是函數獨有的spa

關於更多__proto__更深刻的介紹,能夠參看工業聚大佬的《深刻理解 JavaScript 原型》一文。

顯示原型和隱式原型

  • 每一個函數fun都獨有一個prototype, 及顯式原型(屬性)
  • 每一個實例對象都有一個__proto__, 及隱式原型(屬性)**
  • 對象的隱式原型的值 === 其構造函數的顯示原型的值

怎麼理解呢?咱們經過內存結構圖來看看吧

function Demo() {}
        Demo.prototype.say = () => {       //給原型添加say方法
            console.log("hello world")
        }
        
        console.log(Demo.prototype.say)
        let fn = new Demo();
        
        fn.say();   // 怎麼找到say方法的呢?
        console.log(fn.__proto__ === Demo.prototype)  // true
複製代碼

咱們從圖中能夠看到,Demo函數的原型跟它構造函數(Demo)建立的實例fn.__proto__指向同一個對象。

那麼fn是怎麼找到say方法的呢?

更加具體的說就是經過隱式原型__proro__找到的,分析以下:

  • js引擎執行到fn.say()整行代碼時,解析器去棧中查找fn變量
  • 發現fn變量是引用類型,就去堆內存中查找地址爲0x234的實體,查到後,發現並無say屬性,接着就去找__proro__屬性對應的原型
  • 接着找到內存地址爲0x345對應的實體,發現該實體中有say屬性,一樣的操做去找地址爲0x789的實體,最後執行該函數。

那麼是否是能夠更加準確的說明:實例是經過隱式原型__proto__查找須要調用的屬性的,那麼咱們經過接下來的代碼去驗證一下。

代碼:

function Demo() {}
        Demo.prototype.say = () => {       //給原型添加say方法
            console.log("hello world")
        }
        Demo.prototype.name = 'old name'
        let fn = new Demo();
        
        fn.say();   // 怎麼找到say方法的呢?
        console.log(fn.name)
        console.log("爲修改前",fn.__proto__ === Demo.prototype)  // true
        console.log("-------接下來修改fn的__proto__")
        fn.__proto__ = {
            say: () => {
                console.log("hello 隱式原型")
            },
            name : 'new name'
        }
        console.log("修改實例中的隱式原型",fn.__proto__ === Demo.prototype)  // true
        console.log(fn.name)
        fn.say()
        console.log("從新建立一個Demo構造函數實例")
        let demo1 = new Demo();
        console.log(Demo.prototype === demo1.__proto__)
        demo1.say()
複製代碼

首先的說明的是:

經過查閱相關的文檔,ES6以前不能直接操做隱式原型,也不推薦你這麼作。

經過修改fn的隱式原型,讓它指向一個新的對象。那麼fn.proto 不等於Demo.prototype. 這個例子也能證實一點,實例對象調用屬性時,實例對象不具備該屬性時,是經過隱式原型去找的該屬性的,找不到的話,在它的隱式原型對象隱式原型對象上找。

這也就是咱們常說的,在原型上添加屬性或者方法,實例能夠共享,緣由就在於咱們並不推薦去修改實例的__proto__屬性,這樣子也就是會有一下結果:

function Demo() {
		// 內部語句 this.prototype = {}
}
let fn = new Demo(); // 內部語句: fn.`__proto__` = Demo.prototype

// 實例化一個對象隱式原型會默認賦值: fn.__proto__ = Demo.prototype
// 定義函數時: 顯式原型也會默認添加: Demo.prototype = new Object()
複製代碼

這裏咱們須要知道的是,__proto__是對象所獨有的,而且__proto__一個對象指向另外一個對象,也就是他的原型對象。咱們也能夠理解爲父類對象。它的做用就是當你在訪問一個對象屬性的時候,若是該對象內部不存在這個屬性,那麼就回去它的__proto__屬性所指向的對象(父類對象)上查找,若是父類對象依舊不存在這個屬性,那麼就回去其父類的__proto__屬性所指向的父類的父類上去查找。以此類推,知道找到 null。而這個查找的過程,也就構成了咱們常說的原型鏈

總結

  • 那什麼是原型呢?你能夠這樣理解:每個JavaScript對象(null除外)在建立的時候就會與之關聯另外一個對象,這個對象就是咱們所說的原型,每個對象都會從原型"繼承"屬性。

  • 函數的prototype屬性:在定義函數時自動添加prototype,默認是一個空Object對象

  • 對象的__proto__屬性:建立一個對象實例時,默認值是構造函數的prototype屬性值,也就是上面所說的

  • 實例的構造函數屬性(constructor)指向構造函數

  • 通常而言,能夠直接操做顯式原型,不能直接操做隱式原型(ES6)

  • 更多規範,移步MDN

補充

ObjectFunction的雞和蛋的問題

**最後總結: ** 先有Object.prototype(原型鏈頂端),Function.prototype繼承Object.prototype而產生,最後,Function和Object和其它構造函數繼承Function.prototype而產生。

MDN的推薦

使用__proto__是有爭議的,也不鼓勵使用它。由於它歷來沒有被包括在ECMAScript語言規範中,可是現代瀏覽器都實現了它。__proto__屬性已在ECMAScript 6語言規範中標準化,用於確保Web瀏覽器的兼容性,所以它將來將被支持。它已被不推薦使用, 如今更推薦使用Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf(儘管如此,設置對象的[[Prototype]]是一個緩慢的操做,若是性能是一個問題,應該避免)。

proto 屬性也能夠在對象文字定義中使用對象[[Prototype]]來建立,做爲Object.create() 的一個替代。

prototype chain 原型鏈

最新ES規範給出定義

a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.

如上,在 ECMAScript 2019 規範裏,只經過短短的一句話,就介紹完了 prototype chain

原型鏈的概念,僅僅是在原型這個概念基礎上所做的直接推論。

既然 prototype 只是剛好做爲另外一個對象的隱式引用的普通對象。那麼,它也是對象,也符合一個對象的基本特徵。

每一個對象均可以有一個原型_proto_,這個原型還能夠有它本身的原型,以此類推,
造成一個原型鏈。查找特定屬性的時候,咱們先去這個對象裏去找,
若是沒有的話就去它的原型對象裏面去,
若是仍是沒有的話再去向原型對象的原型對象裏去尋找......
這個操做被委託在整個原型鏈上,這個就是咱們說的原型鏈了。
複製代碼

結論

  • __proto__ 是原型鏈查詢中實際用到的,它老是指向 prototype

  • prototype 是函數所獨有的**,**在定義構造函數時自動建立,它老是被 proto 所指。

  • 全部對象都有__proto__屬性,函數這個特殊對象除了具備__proto__屬性,還有特有的原型屬性prototype。prototype對象默認有兩個屬性,constructor屬性和__proto__屬性。prototype屬性能夠給函數和對象添加可共享(繼承)的方法、屬性,而__proto__是查找某函數或對象的原型鏈方式。constructor,這個屬性包含了一個指針,指回原構造函數。

參考

深刻理解 JavaScript 原型

一文吃透全部JS原型相關知識點

JavaScript深刻之從原型到原型鏈

從__proto__和prototype來深刻理解JS對象和原型鏈

相關文章
相關標籤/搜索