“影子属性”指在派生对象上设置与原型同名的自有属性以遮蔽原型属性,读取优先实例值、删除后回退原型值;Object.create()构建原型链,赋值操作自然实现该行为。

Object.create() 本身不直接创建“影子属性”,但它能构建精准控制属性可见性的原型链结构,从而实现一种逻辑上类似“影子属性”的拷贝行为:即实例上可读写同名属性,但不污染原型,且能自然回退到原型值(当实例未定义时)。
什么是“影子属性”在此语境下的含义
这里说的“影子属性”不是 JavaScript 标准术语,而是指:在派生对象上设置一个与原型上同名的自有属性,从而“遮蔽(shadow)”原型上的该属性——读取时优先取实例值,删除后自动回退到原型值。这本质是原型链属性查找机制的自然体现,而非特殊语法支持。
利用 Object.create() 创建新对象并指定其 [[Prototype]],再配合有意识地赋值/删除,就能稳定复现这种行为。
基础影子拷贝模式:只继承可读属性,写操作仅影响实例
目标:让新对象“看起来像拷贝”,但所有写入都落在自身,不影响源;读取时若自身无则沿原型链取源值。
- 用 Object.create(source) 创建新对象,source 作为其原型
- 此时新对象自身无任何自有属性,in 操作符查不到,但 source.propertyIsEnumerable(key) 或 Object.getOwnPropertyDescriptor(source, key) 可确认源属性是否可枚举/可配置
- 对新对象执行 obj.key = value,会添加自有属性,覆盖原型链上的同名访问 —— 这就是“影子”形成过程
- 执行 delete obj.key 后,再次读取 obj.key 就自动回退到 source.key
增强版:支持选择性拷贝 + 影子语义混合使用
实际中常需部分属性立即拷贝为自有(如必须初始化的),其余保留影子语义。可结合 Object.assign() 与 Object.create():
- 先用 Object.create(source) 创建原型链干净的对象
- 再用 Object.assign(target, { a: 1, b: 'init' }) 显式设置若干初始自有属性(这些就是“提前影子化”的属性)
- 其余属性仍保持“按需影子”:首次赋值才生成自有属性,之前读取全走原型
- 注意:Object.assign() 不会触发 setter,也不受原型上 getter 影响(它只写自有属性)
注意事项与边界情况
这种模式简洁有效,但需留意几点:
- 不可枚举属性不会被 for...in 遍历到,即使在原型上存在;Object.keys() 也只返回自有可枚举属性 —— 若需完整遍历,要用 Object.getOwnPropertyNames() + 自行遍历原型链
- getter/setter 属性会被正确继承和触发:若 source.x 是 getter,则 obj.x 触发 source 的 getter;一旦 obj.x = v,就变成自有数据属性,后续不再触发 getter
- 引用类型属性仍共享:若 source.data = {a:1},则 obj.data.a = 2 会修改 source.data,因为 obj.data 和 source.data 指向同一对象 —— “影子”只对属性本身(key→value 绑定)起作用,不深拷贝值
- 想避免引用共享,需在首次访问时惰性深拷贝(即“影子+懒加载”),但这已超出 Object.create() 职责,需额外封装
不复杂但容易忽略。核心就一条:Object.create() 提供原型链骨架,赋值操作天然产生影子属性,无需额外魔法。










