为何对象原型链上部分对象的constructor.prototype不指向其[[prototype]]?
anyObject.constructor.prototype不等于Object.getPrototypeOf(anyObject)? 背景
可用于打印原型链的源对象种类很多,比如:
[1, 2, 3]// 数组字面量{a: 1}// 对象字面量new Set()// 内置Setnew Promise(() => {})// 内置Promisefunction foo(){}// 作为对象的函数window// DOM全局window对象
实际上任意对象都能作为原型链的源对象。
我原本以为下面这个表达式永远成立:
anyObject.constructor.prototype === Object.getPrototypeOf(anyObject)
用这段代码打印原型链及每个对象的constructor:
function chain(o) { while (o) { console.dir(o); let tip = ''; if (o.constructor.prototype === Object.getPrototypeOf(o)) tip = 'points to the next object on the chain, i.e. its [[prototype]].'; else if (o.constructor.prototype === o) tip = 'points to the above object itself.'; else tip = 'NEVER EXECUTED TO HERE'; console.log('Its constructor:', o.constructor, tip) o = Object.getPrototypeOf(o); } }
但实际运行后发现,原型链上不是所有对象都符合这个预期,该怎么理解这种设计逻辑?
核心原因:constructor是可修改的普通属性,[[Prototype]]是对象的内置关联
1. 多数场景下的一致性
大多数普通对象确实符合你的预期:
- 比如
const arr = [1,2,3],arr.constructor指向Array构造函数,Array.prototype就是arr的内置原型([[Prototype]]),所以等式成立。 - 普通字面量对象
{a:1}同理,constructor指向Object,Object.prototype就是它的原型。
2. 打破一致性的典型场景
(1)原型链的终点:Object.prototype
Object.prototype是所有普通对象原型链的顶端,它的[[Prototype]]是null。但它的constructor指向Object构造函数,而Object.prototype正好是Object的prototype属性值——所以Object.prototype.constructor.prototype就是它自己,不等于null,这时就会触发你代码里的第二个分支。
这是JS语言的基础设计:Object.prototype没有上层原型,而它的构造函数Object的原型属性就是它本身。
(2)被修改过构造函数或原型的对象
如果手动修改了构造函数的prototype,或者直接修改对象的constructor属性,等式就会失效:
function Person() {} const p = new Person(); // 替换构造函数的原型对象 Person.prototype = { name: 'test' }; // p的内置原型还是原来的Person.prototype,但p.constructor.prototype是新对象 console.log(p.constructor.prototype === Object.getPrototypeOf(p)); // false
因为p的constructor是继承自旧原型的constructor(指向Person),但Person的prototype已经被替换,两者自然不相等。
(3)宿主对象的特殊实现
像window这种浏览器宿主对象,它的原型链规则不一定完全遵循普通JS对象。比如window的constructor指向Window,但Window.prototype可能和window的内置原型不是同一个对象——这是浏览器厂商对全局对象的特殊设计。
另外像Array.prototype这种内置原型对象本身,它的constructor是Array,但它的内置原型是Object.prototype,所以Array.prototype.constructor.prototype(即Array.prototype)不等于Object.getPrototypeOf(Array.prototype)(即Object.prototype),也会触发第二个分支。
3. 设计逻辑的本质
constructor只是个可枚举、可修改的普通属性,本质是给对象打个“构造来源”的标签,但这个标签可以被随意修改,没有强制绑定关系。[[Prototype]](通过Object.getPrototypeOf()访问)是对象的内置内部槽,是原型链查找的核心依据,它的关联在对象创建时就确定(除非用Object.setPrototypeOf()修改),是原型链的真实源头。
简单说:constructor是可撕可换的“标签”,[[Prototype]]才是对象真正的“原型父级”。
内容的提问来源于stack exchange,提问作者Zhang Wei

