讲解了 __proto__, [[prototype]] 和 prototype 三者的关系, 并用 js 实现了一个 __proto__
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.prototype 是任何函数的原型, 比如 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]] 关联了起来, 形成了一个链式结构 --- 原型链. 如果没把例子举起来, 是我不会讲故事, 请点这里)看图理解.
[为什么不推荐使用 Object.prototype.__proto__?]
- 虽然所有现代浏览器都实现了该访问器属性, ES6 (ECMA2015) 及之后的标准也暂时包含了它, 但是它的存在只是为了确保规范与浏览器兼容
如果不得不使用 Object.prototype.__proto__ 呢?
推荐使用 Object.getPrototypeOf() 方法代替 Object.prototype.__proto__
为什么不推荐修改 [[prototype]]?
- 操作 [[prototype]] 属性 (只要该属性变更了), 各个浏览器引擎针对 prototype 相关的优化(缓存)会失效, 这就导致访问原型上的属性很慢
如果不得不修改 [[prototype]] 呢?
虽然原型只是对象,但它们由 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 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些.)