Object.prototype.__proto__, [[prototype]] 和 prototype
阿新 • • 發佈:2020-08-16
## `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 只是在兩個物件之間建立一個關聯,這樣,一個物件就可以通過委託訪問另一個物件的屬性和函式,所以與其叫繼承,委託的說法反而更準