Object.prototype.__proto__
是什麼?
-
__proto__
是一個訪問器屬性, 用於訪問物件的原型[[prototype]]
(見以下模擬的getter
和setter
方法, 不一定完全與規範一致, 僅供參考)-
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; }
-
-
通過它可以訪問到物件的
[[prototype]]
, 也即物件的原型 -
[[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]]
關聯了起來, 形成了一個鏈式結構 --- 原型鏈. 如果沒把例子舉起來, 是我不會講故事, 請點這裡)看圖理解.
為什麼不推薦使用它?
- 雖然所有現代瀏覽器都實現了該訪問器屬性. ES6 (ECMA2015) 及之後的標準也暫時包含了它, 它的存在只是為了確保規範與瀏覽器相容
- 操作
[[prototype]]
屬性 (只要該屬性變更了), 各個瀏覽器引擎針對prototype
相關的優化會失效, 這就導致訪問原型上的屬性很慢
如果不得不使用呢?
-
推薦使用
Object.getPrototypeOf()
方法代替Object.prototype.__proto__
-
雖然原型只是物件,但它們由 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
物件沒有原型. 附一張圖 (圖片來源於這裡):
這張圖說明了 JavaScript 的繼承 (委託): new Foobar
--- __proto__
---> Foobar.prototype
--- __proto__
---> Object.prototype
--- __proto__
---> null
.
(用《你不知道的JavaScript》裡的話來說: 繼承意味著複製操作,然而 JavaScript 預設並不會複製物件的屬性,相反,JavaScript 只是在兩個物件之間建立一個關聯,這樣,一個物件就可以通過委託訪問另一個物件的屬性和函式,所以與其叫繼承,委託的說法反而更準確些.)