天天看點

深入了解js原型和原型鍊(prototype chain)

深入了解js原型和原型鍊(prototype chain)

這篇文章不能讓你了解他們的底層是如何實作的,但可以讓你知道這些屬性是什麼,有什麼用,誰與其關聯

  • 首先要知道的概念
    • js 萬物皆對象
    • 内置類型:null,undefined,boolean,number,string,symbol,object 拓展資料1 拓展資料2
      • 引用類型Object:Array ,Function, Date, RegExp等
      • 基本類型:null,undefined,boolean,number,string,symbol
    • 原型
      • 隐式原型:所有引用類型,也可以認為是目前帶有__proto__屬性的對象,他們帶有的這個__proto__屬性就是該對象的原型。
      • 顯示原型:所有函數擁有prototype屬性(顯式原型)(僅限函數)
      • 原型對象(構造函數):擁有prototype屬性的對象,在定義函數時就被建立
    • 引用屬性或調用方法
      • 1.對象自身:debug檢視該屬性時,不是在該對象原型上的屬性,而是在最外層的屬性
      • 2.當調用某種方法或查找某種屬性時,首先會在自身調用和查找,如果對象自身并沒有該屬性或方法,則會去它的原型__proto__屬性中調用查找,如果__proto__中還有__proto__,那麼會繼續查找,直到最頂級頂級原型。
    • 執行個體化
      • 1.執行個體化實際上是将 函數(構造函數)執行個體化成為一個執行個體對象,實際執行個體化的是構造函數的prototype屬性,并将其執行個體化到執行個體對象的__proto__原型上
      • 2.由上面一條可得知:執行個體對象想要調用函數的方法,需要将函數的方法定義在函數的prototype屬性上【或者定義在函數體内(此種是function所特有的)】
      • 執行個體對象:将函數(構造函數)new出來. 并且執行個體對象.proto === 構造函數.prototype,我們也可以通過該式子判斷執行個體對象的構造函數是不是它【因為<執行個體化-1.>】
    • 構造函數
      • 1.所有函數,諸如class aa {}的aa,function aa{}, Function,Array等,都稱作構造函數,并且 String === String.prototype.constructor === String.prototype.constructor.prototype.constructor // true
    • 原型鍊
      • 1.js所有函數(方法function),類(class),的原型鍊頂級原型都是Object,二級都是Function
      • 2.通過__proto__構成的原型鍊,而非prototype
      • 3.如果對象自身和它的原型,都定義了一個同名屬性,那麼優先讀取對象自身的屬性,這叫做“覆寫”(overiding)
      • 4.一級級向上在原型鍊尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,将會周遊整個原型鍊

        可以參考下面這張圖了解原型鍊,當然最好還是自己debug看一下就明白了

        深入了解js原型和原型鍊(prototype chain)
        深入了解js原型和原型鍊(prototype chain)
    • 繼承
      • 僞繼承
  • 舉例
    • 引用屬性或調用方法
    // 對象自身
      function A(){ this x = 1; }
      A.prototype.s = 2;
      const a = new A();
      // debug檢視a:A: { x: 1 }, a.x = 1; 這個x就是自身屬性。
      // a中還有原型__proto__: { s: 2 }, 這個s就是原型上的屬性,實際就是繼承自構造函數A.prototype的屬性
               
    • 執行個體化
    function A(){
        this.id = 1; // 将A函數執行個體化為執行個體對象之後自身才會有該屬性: A {id: 1}
      }
      A.prototype.id2 = 2; // 不用執行個體化,自身就帶有的屬性,通過A.prototype.id2可以通路其屬性
      // 此時執行個體對象na就相當于“繼承”了構造函數A的prototype屬性,并将A函數内部的id屬性添加到自身。
      // 得到如下 A {id: 1}, 隐式原型__proto__:{ id2: 2 },實際就是 A { id: 1, __proto__:{ id2: 2 } }
      const na = new A(); 
               
    • 原型鍊
    /**
     * 這裡有一點需要注意!<原型鍊1.>【Object函數同樣也是Function的執行個體對象】,但最終原型鍊最頂級都是* Object。
     * 原型鍊頂級Object和此處的Object函數通過檢視Object函數的__proto__原型可以檢視出之間的不同處
     * Object函數包括<對象自身>攜帶的方法屬性,而頂級Object不包含對象自身的方法就是最原始的Object,不帶那些方法。
     * 這應該是因為所有的函數都繼承于Object,又由于<引用屬性或調用方法2.><執行個體化1.>,是以為了不讓所有函數都“繼承”Object的方法,就定義在了對象自身。
     **/
     /**
      * Object與Function之間的“繼承”關系
      1. Function.__proto__.__proto__ === Object.prototype // true
      Function.__proto__.__proto__.constructor === Object  // true
      2. Object.__proto__.constructor === Function // true, function是Object的構造函數
      而是1. Object.prototype === Function.prototype.
      **/
      // 1. 通過debug檢視Object, Number。。。等的__proto__屬性,一級一級點開發現最後兩級的原型都是function(){ [native code] }和Object
      Int16Array.prototype
      Int32Array.prototype
      Function.prototype.f = "function";
      Object.prototype.o = "object";
      String.prototype.s = "string";
      Number.prototype.n = "number";
      Boolean.prototype.b = "boolean"
      Array.prototype.a = "array"
      Date.prototype.d = "date"
      function Person(){}
      // 由于<原型鍊的1.>以及<引用屬性或調用方法的2.>以及<執行個體化的1.>,也就是說Number相當于Function的執行個體對象,而
      console.log(Number.o); // object
      console.log(Number.f); // function
      console.log(Number.n); // undefiend
               
    • 構造函數
    String.prototype.s = "string";
      String === String.prototype.constructor
      String === String.prototype.constructor.prototype.constructor // true
      // 通過任意引用類型對象 原型上的constructor屬性可以拿到,他的構造函數
      function A(){ this x = 1; }
      const a = new A();
      a.__proto__.constructor === A; // true
      // 通過任意引用類型對象(對象自身帶prototype屬性)上的constructor屬性,可以拿到他自己
      String.prototype.constructor = String // true
    
               

參考資料1

參考資料2[其中有一處問題,原型鍊是隐式原型__proto__構成的原型鍊,最後到get __proto__值為null結束,和prototype無關]

參考資料3

繼續閱讀