1. 程式人生 > 程式設計 >JavaScript 原型與原型鏈詳情

JavaScript 原型與原型鏈詳情

目錄
  • 1、prototype(顯式原型)
  • 2、__proto__(隱式原型)
  • 3、constructor(建構函式)
  • 4、new的原理
  • 5、原型鏈
    • 5.1 原型鏈的作用
    • 5.2 建構函式的__proto__是什麼呢?
  • 6、總結

    前言:

    常被描述為一種「基於原型的語言」——每個物件都擁有一個「原型物件」,物件以其原型為模板、從原型繼承屬性和放法。原型物件也可能擁有原型,並從中繼承屬性和方法,一層一層以此類推。這種關係常被稱為「原型鏈」,它解釋了為何一個物件會擁有定義在其他物件中的屬性和方法。

    準確的說,這些屬性和方法定義在Object的建構函式的prototype屬性上,而非物件例項本身。

    四句話道破原型與原型鏈:


    • 每個函式(類)天生自帶一個屬性prototype,屬性值是一個物件,裡面儲存了當前類供例項使用的屬性和方法 「(顯示原型)」
    • 在瀏覽器預設給原型開闢的堆記憶體中有一個constructor屬性:儲存的是當前類本身(⚠️注意:自己開闢的堆記憶體中預設沒有constructor屬性,需要自己手動新增)「(建構函式)」
    • 每個物件都有一個__proto__屬性,這個屬性指向當前例項所屬類的原型(不確定所屬類,都指向Object.prototype)「(隱式原型)」
    • 當你試圖獲取一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼它會去它的隱式原型__proto__(也就是它的建構函式的顯示原型prototype
      )中查詢。「(原型鏈)」

    建構函式,原型與例項的關係:

    每個建構函式(constructor)都有一個原型物件(prototype),原型物件(prototype)都包含一個指向建構函式(constructor)的指標,而例項(instance)都包含一個指向原型物件(__proto__)的內部指標

    1、prototype(顯式原型)

    每個函式都有一個prototype屬性

    // 建構函式(類)
    function Person(name){
        this.name = name
    }
    // new了一個例項 (物件)
    var person = new Person('南玖')
    console.log(person) //Person { name: '南玖' }
    console.log(Person.prototype)  //建構函式(類)的原型 --
    --->物件 Person.prototype.age = 18 // 建構函式原型 console.log(person.age) // 18

    上面我們把這個函式Person的原型打印出來了,它指向的是一個物件,並且這個物件正是呼叫該建構函式而建立的例項的原型

    JavaScript 原型與原型鏈詳情

    上面這張圖表示的是建構函式與例項原型之間的關係,所以我們知道了建構函式的prototype屬性指向的是一個物件。

    那例項與例項原型之間的關係又是怎樣的呢?這裡就要提到__proto__屬性了

    2、__proto__(隱式原型)

    從上面四句話中我們可以知道這是每一個script物件(除null)都具有的一個屬性,這個屬性會指向該物件的原型(也就是例項原型)

    因為在JavaScript中沒有類的概念,為了實現類似繼承的方式,通過__proto__將物件和原型聯絡起來組成原型鏈,的以讓物件訪問到不屬於自己的屬性。

    那麼我們就能夠證明例項與例項原型之間的關係

    console.log(person.__proto__)  //例項(物件)的原型--->物件
    
    console.log(person.__proto__ === Person.prototype)  //例項的原型與建構函式的原型相等
    

    JavaScript 原型與原型鏈詳情

    從上圖我們可以看出例項物件與建構函式都可以指向原型,那麼原型能不能指向建構函式或者是例項呢?

    3、constructor(建構函式)

    原型是沒有屬性指向例項的,因為一個建構函式可以建立多個例項物件;

    從前面的四句話中我們知道「在瀏覽器預設給原型開闢的堆記憶體中有一個constructor屬性」,所以原型也是可以指向建構函式的,這個屬性就是「constructor

    於是我們可以證明一下觀點:

    console.log(Person.prototype.constructor) //例項的顯式原型的建構函式ƒ Person(name){this.name = name}
    console.log(person.__proto__.constructor)  //例項的隱式原型的建構函式 ƒ Person(name){this.name = name}
    console.log(person.__proto__.constructor === Person.prototype.constructor)//true 例項原型的建構函式與類的建構函式相等
    console.log(Person === Person.prototype.constructor)  //true
    
    
    

    JavaScript 原型與原型鏈詳情

    例項物件的__proto__是如何產生的?
    我們知道當我們使用new 操作符時,生成的例項物件就擁有了__proto__屬性

    function Foo() {}
    // 這個函式時Function的例項物件
    // function是一個語法糖
    // 內部其實呼叫了new Function()
    
    
    

    所以可以說,在new的過程中,新物件被添加了__proto__屬性並且連結到了建構函式的原型上。

    4、new的原理

    說簡單點可以分為以下四步:

    • 新建一個空物件
    • 連結原型
    • 繫結this,執行建構函式
    • 返回新物件
    function myNew() {
    // 1.新建一個空物件
    let obj = {}
    // 2.獲得建構函式
    let con = arguments.__proto__.constructor
    // 3.連結原型
    obj.__proto__ = con.prototype
    // 4.繫結this,執行建構函式
    let res = con.apply(obj,arguments)
    // 5.返回新物件
    return typeof res === 'object' ? res : obj
    }
    
    

    5、原型鏈

    說完了原型,我們再來看看什麼是原型鏈?先來看一張圖:

    JavaScript 原型與原型鏈詳情

    這張圖中,由__proto__串起來的鏈式關係,我們就稱它為原型鏈

    5.1 原型鏈的作用

    原型鏈決定了JavaScript中繼承的實現方式,當我們訪問一個屬性時,它的查詢機制如下:

    • 訪問物件例項屬性,有的話直接返回,沒有則通過__proto__去它的原型物件上查詢
    • 原型物件上能找到的話則返回,找不到繼續通過原型物件的__proto__查詢
    • 一直往下找,直到找到Object.prototype,如果能找到則返回,找不到就返回undefined,不會再往下找了,因為Object.prototype.__proto__是null,說明了Object是所有物件的原型鏈頂層了。

    從圖中我們可以發現,所有物件都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個物件,但是這個物件卻不是 Object 創造的,而是引擎自己建立了 Object.prototype 。所以可以這樣說,所有例項都是物件,但是物件不一定都是例項。

    5.2 建構函式的__proto__是什麼呢?

    由上面的原型鏈的解釋,我們應該能夠理解建構函式的__proto__的,在JavaScript中所有東西都是物件,那麼建構函式肯定也是物件,是物件就有__proto__

    function Person(){}
    console.log(Person.__proto__)
    console.log(Function.prototype)
    console.log(Person.__proto__===Function.prototype) //ZChgBCFhw true
    
    
    

    「這也說明了所有函式都是Function的例項」

    那這麼理解的話,Function.__proto__豈不是等於Function.prototype。。。。我們不妨來列印一下看看

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

    打印出來確實是這樣的。難道 Function.prototype 也是通過 new Function() 產生的嗎?

    答案是否定的,這個函式也是引擎自己建立的。首先引擎建立了 Object.prototype ,然後建立了 Function.prototype ,並且通過 __proto__ 將兩者聯絡了起來。這裡也很好的解釋了上面的一個問題,為什麼 let fun = Function.prototype.bind() 沒有 prototype 屬性。因為 Function.prototype 是引擎創建出來的物件,引擎認為不需要給這個物件新增 prototype 屬性。

    6、總結

    • Object 是所有物件的爸爸,所有物件都可以http://www.cppcns.com通過 __proto__ 找到它
    • Function 是所有函式的爸爸,所有函式都可以通過 __proto__ 找到它
    • Function.prototype Object.prototype 是兩個特殊的物件,他們由引擎來建立
    • 除了以上兩個特殊物件,其他物件都是通過構造器 new 出來的
    • 函式的 prototype 是一個物件,也就是原型
    • 物件的 __proto__ 指向原型, __proto__ 將物件和原型連線起來組成了原型鏈

    到此這篇關於JavaScript 原型與原型鏈詳情的文章就介紹到這了,更多相關JavaScript 原型與原型鏈內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!