1. 程式人生 > >Object.prototype.__proto__, [[prototype]] 和 prototype

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

## `Object.prototype.__proto__` 是什麼? 1. [`__proto__` 是一個訪問器屬性](https://tc39.es/ecma262/#sec-additional-properties-of-the-object.prototype-object), 用於訪問物件的原型 `[[prototype]]` (見以下模擬的 `getter` 和 `setter` 方法, 不一定完全與規範一致, 僅供參考) * `get Object.prototype.__proto__` ```javascript 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__` ```javascript 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` 的關係 舉個例子 (一定要舉起來啊!): ```javascript 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]]` 關聯了起來, 形成了一個鏈式結構 --- 原型鏈. 如果沒把例子舉起來, 是我不會講故事, 請點[這裡](https://img2020.cnblogs.com/blog/2100665/202008/2100665-20200815204306720-1324243877.png))看圖理解. ## [為什麼不推薦使用它?](https://hijiangtao.github.io/2018/08/21/Prototypes/) 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 為對應的包裝物件, 所以只有 `null` 和 `undefined` 不能訪問到`Object.prototype.__proto__` ## `Object.prototype.__proto__` 的值是 `null`, 然後呢? 眾所周知, `Object.prototype.__proto__` 的值是 `null`, 通常來說也是一個物件的原型鏈終點, 它表示了 `Object.prototype` 物件沒有原型. 附一張圖 (圖片來源於[這裡](https://blog.oyanglul.us/javascript/understand-prototype.html)): ![prototype](https://img2020.cnblogs.com/blog/2100665/202008/2100665-20200815204306720-1324243877.png) 這張圖說明了 JavaScript 的繼承 (委託): `new Foobar` --- `__proto__` ---> `Foobar.prototype` --- `__proto__` ---> `Object.prototype` --- `__proto__` ---> `null`. (用《你不知道的JavaScript》裡的話來說: 繼承意味著複製操作,然而 JavaScript 預設並不會複製物件的屬性,相反,JavaScript 只是在兩個物件之間建立一個關聯,這樣,一個物件就可以通過委託訪問另一個物件的屬性和函式,所以與其叫繼承,委託的說法反而更準