Object.prototype.__proto__, [[prototype]] 和 prototype

ayuuuuuu發表於2020-08-15

Object.prototype.__proto__ 是什麼?

  1. __proto__ 是一個訪問器屬性, 用於訪問物件的原型 [[prototype]] (見以下模擬的 gettersetter 方法, 不一定完全與規範一致, 僅供參考)

    • get Object.prototype.__proto__

      get __proto__() {
          // Let O be ? ToObject(this value).
          if(this === void(0) || this === null) {
              throw TypeError(`Cannot read property '__proto__' of ${this}`);
          }
          let O = Object(this);  // this !== null 或 undefined 時, Return ! ToObject(value);
          // Return ? O.[[GetPrototypeOf]]().
          return Object.getPrototypeOf(O);
      }
      
    • set Object.prototype.__proto__

      set __proto__(proto) {
          // Let O be ? RequireObjectCoercible(this value).
          if(this === void(0) || this === null) {
              throw TypeError(`Cannot set property '__proto__' of ${this}`);
          }
          let O = this;  // this !== null 或 undefined 時, return argument;
          // If Type(proto) is neither Object nor Null, return undefined.
          if (typeof proto !== 'object') {  // typeof null === 'object'
              return;
          }
          // If Type(O) is not Object, return undefined.
          if (typeof O !== 'object') {  // O !== null 或 undefined 
              return;
          }
          // Let status be ? O.[[SetPrototypeOf]](proto).
          // If status is false, throw a TypeError exception.
          // Return undefined.
          Object.setPrototypeOf(O, proto);
          return;
      }
      
  2. 通過它可以訪問到物件的 [[prototype]], 也即物件的原型

  3. [[prototype]] 的值是該物件的原型或 null (對於 Object.prototype 物件而言, 其沒有原型, 返回null: Object.prototype.__proto__; // null)

[[prototype]]prototype 的關係

舉個例子 (一定要舉起來啊!):

class Person {
    constrctor(name, age) {
        this.name = name;
    }
}

let p1 = new Person('ayu');

// 對於例項 p1 來說, 它的原型 [[prototype]] 是 Person 物件的 prototype 屬性值. 也即例項 p1 的原型是 Person.prototype
Object.getPrototypeOf(p1) === Person.prototype;  // true

// 順便再說下 constructor
// 例項由原型中的 constructor 屬性值構造, 也即例項 p1 由 Person (Object.getPrototypeOf(p).constructor) 構造
Object.getPrototypeOf(p1).constructor === Person;  // true
p1.constructor === Person;  // true
// 例項與 constructor 的關係為 n : 1, 因此每個例項的構造器均指向 Person
let p2 = new Person('ayu2');
p1.constructor === p2.constructor;  // true
// 任何函式都是由 Function 構造的, 比如 Object, Person 等, 比較特殊的是: Function.constructor === Function
Person.constructor === Function;  // true
Function.constructor === Function;  // true

// 再來說下原型鏈
// Function.protype 是任何函式的原型, 比如 Object, Person 等, 比較特殊的是 Object.getPrototypeOf(Function) === Function.prototype
Object.getPrototypeOf(Person) === Function.prototype;  // true
Object.getPrototypeOf(Function) === Function.prototype;  // true
// 最後, 任何原型的原型最終都追溯到 Object.prototype 或 null. 這形成了一個鏈式結構, 它被叫做原型鏈
Object.getPrototypeOf(Person.prototype) === Object.prototype;  // true
Object.getPrototypeOf(Object.getPrototypeOf(Person)) === Object.prototype;  // true
Object.getPrototypeOf(Function.prototype) === Object.prototype;  // true
Object.getPrototypeOf(Object.getPrototypeOf(Object)) === Object.prototype;  // true
Object.getPrototypeOf(Object.prototype) === null;  // true

綜上, [[prototype]] 表示了一個例項的原型 (prototype 屬性的值表示了其例項的原型物件), 物件與物件之間通過 [[prototype]] 關聯了起來, 形成了一個鏈式結構 --- 原型鏈. 如果沒把例子舉起來, 是我不會講故事, 請點這裡)看圖理解.

為什麼不推薦使用它?

  1. 雖然所有現代瀏覽器都實現了該訪問器屬性. ES6 (ECMA2015) 及之後的標準也暫時包含了它, 它的存在只是為了確保規範與瀏覽器相容
  2. 操作 [[prototype]] 屬性 (只要該屬性變更了), 各個瀏覽器引擎針對 prototype 相關的優化會失效, 這就導致訪問原型上的屬性很慢

如果不得不使用呢?

  1. 推薦使用 Object.getPrototypeOf() 方法代替 Object.prototype.__proto__

  2. 雖然原型只是物件,但它們由 JavaScript 引擎專門處理,以優化在原型上查詢方法的效能表現。把你的原型放在一旁!或者,如果你確實需要修改原型,請在其他程式碼執行之前執行此操作,這樣至少不會讓引擎所做的優化付諸東流。

JavaScript 中誰不能訪問到 Object.prototype.__proto__?

  • 原型鏈上沒有 Object.prototype 物件的物件, 均不能訪問
    • 比如使用 Object.create(null) 建立的物件或我們變更了其原型的物件 obj.__proto__ = null, 該類物件不能訪問 Object.prototype.__proto__ (但我們可以通過 Object.getPrototypeOf(obj) 訪問其原型: Object.getPrototypeOf(Object.create(null)); // null)
  • 沒有原型的原始值
    • 一般來說, null, undefined, number, string, boolean, symbol, bigint 這些基本資料型別的 (原始) 值沒有原型 (Object.getPrototypeOf(null); // TypeError: Cannot convert undefined or null to object, 原始值不可能有原型), 所以其無法訪問到 Object.prototype.__proto__. 但鑑於除了 null, undefined 以外的基本資料型別值在運算時會自動裝箱 autoboxing 為對應的包裝物件, 所以只有 nullundefined 不能訪問到Object.prototype.__proto__

Object.prototype.__proto__ 的值是 null, 然後呢?

眾所周知, Object.prototype.__proto__ 的值是 null, 通常來說也是一個物件的原型鏈終點, 它表示了 Object.prototype 物件沒有原型. 附一張圖 (圖片來源於這裡):

prototype

這張圖說明了 JavaScript 的繼承 (委託): new Foobar --- __proto__ ---> Foobar.prototype --- __proto__ ---> Object.prototype --- __proto__ ---> null.

(用《你不知道的JavaScript》裡的話來說: 繼承意味著複製操作,然而 JavaScript 預設並不會複製物件的屬性,相反,JavaScript 只是在兩個物件之間建立一個關聯,這樣,一個物件就可以通過委託訪問另一個物件的屬性和函式,所以與其叫繼承,委託的說法反而更準確些.)

相關文章