Object.prototype.__proto__, [[prototype]] 和 prototype
- 2020 年 8 月 15 日
- 筆記
- __proto__, [[prototype]], javascript, prototype, 原型對象, 原型鏈
Object.prototype.__proto__
, [[prototype]]
和 prototype
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 只是在兩個對象之間創建一個關聯,這樣,一個對象就可以通過委託訪問另一個對象的屬性和函數,所以與其叫繼承,委託的說法反而更準確些.)