元素ID映射至window对象属性的机制及属性描述符查询异常解析
这问题挺有意思的,我来给你拆解一下背后的浏览器机制~
先看你贴的这段代码和对应的输出:
const owner = Object.getPrototypeOf(Object.getPrototypeOf(window)) const desc = Object.getOwnPropertyDescriptor(owner, "experiment"); const allDesc = Object.getOwnPropertyDescriptors(owner); console.log(experiment); /// <div id="experiment">Test</div> console.log(owner); /// WindowProperties {Symbol(Symbol.toStringTag): 'WindowProperties'} console.log(desc); /// {value: div#experiment, writable: true, enumerable: false, configurable: true} console.log(allDesc); /// {Symbol(Symbol.toStringTag): {value: 'WindowProperties', writable: false, enumerable: false, configurable: true}}
<div id="experiment">Test</div>
你发现的矛盾点确实反直觉:明明能从owner(也就是window原型链上的WindowProperties对象)拿到experiment的属性描述符,但用Object.getOwnPropertyDescriptors查所有描述符时却找不到它——这本质上是浏览器的历史兼容机制+特殊内部查找逻辑共同作用的结果。
核心逻辑拆解
WindowProperties对象的本质
你通过两层原型链拿到的owner是WindowProperties,它是window原型链上的一个特殊内置对象,自身只拥有Symbol.toStringTag这一个真实属性,其他看起来“存在”的属性都不是它自身的。ID映射到全局的兼容特性
早年间前端开发者习惯直接用DOM元素的ID作为全局变量访问(比如直接写experiment而不是document.getElementById('experiment')),为了兼容这种历史写法,浏览器实现了一个特殊的动态属性查找逻辑:
当你访问window上不存在的属性时,浏览器会自动在页面中查找对应ID的DOM元素;更特殊的是,当你尝试用Object.getOwnPropertyDescriptor在window的原型链对象上查询某个ID对应的“属性”时,浏览器会触发内部的[[GetOwnProperty]]方法,动态生成一个属性描述符返回给你——但这个“属性”从来没有真正挂载到WindowProperties对象上。两个API的行为差异
Object.getOwnPropertyDescriptor(owner, "experiment"):这个API会触发对象的内部查找逻辑,浏览器识别到你查询的是一个DOM元素ID,就动态生成对应的属性描述符返回,哪怕这个属性不是owner自身的。Object.getOwnPropertyDescriptors(owner):这个API的逻辑是只返回对象自身真正拥有的属性描述符,不会触发这个特殊的ID映射查找,所以只能拿到WindowProperties自身的Symbol.toStringTag描述符,找不到experiment的。
结合DOM规范的补充
你提到DOM规范只定义了ID的属性变更步骤,没错——规范本身并没有要求把ID映射为全局属性,这完全是浏览器厂商为了兼容旧代码额外实现的特性,属于规范之外的实现细节。
再回头看你的输出
- 直接
console.log(experiment)能拿到元素,本质上是window.experiment的简写,触发了全局的ID查找逻辑; - 你拿到的
desc是浏览器临时生成的描述符,不是owner对象自身的属性,自然不会出现在allDesc的结果里。
备注:内容来源于stack exchange,提问作者MaximPro

