javascript中实例属性优先于原型属性,删除实例属性后原型属性自动生效;引用类型须在构造函数中初始化以防共享;方法重名是设计选择,可覆盖、增强或重命名。

对象实例与原型共享属性时,所谓“冲突”其实不是错误,而是 JavaScript 属性查找机制的自然结果:实例属性永远优先于原型链上的同名属性。关键在于明确控制权归属——你想让谁生效、谁被忽略、谁该初始化、谁该隔离。
实例属性遮蔽原型是默认行为
当实例自身拥有某个属性(比如 obj.name = 'Alice'),哪怕 Constructor.prototype.name 也存在,读取 obj.name 时永远返回实例值。这是“就近原则”的体现,无需额外干预。
- 删除实例属性后,原型上的同名属性自动恢复可见:delete obj.name → 再访问 obj.name 就取自原型
- 想临时绕过实例、强制调用原型方法?用 Constructor.prototype.method.call(obj)
- 遍历对象时(如 for...in),原型可枚举属性也会出现,需用 obj.hasOwnProperty(key) 过滤才得纯实例属性
引用类型绝不能挂原型上
数组、对象、函数等引用类型若直接写在 prototype 上(如 MyClass.prototype.items = []),所有实例将共享同一份内存,一个改,全部变。
- 正确做法:在构造函数或 constructor 中初始化 —— this.items = []
- ES6 class 同样适用:把 this.data = {} 放在 constructor() 里,别写在类体顶层
- 若必须在原型方法中操作引用属性,先检查 this.xxx === undefined 再赋新值,实现懒初始化
合并多个对象时的覆盖逻辑要主动管理
Object.assign() 或展开运算符({...a, ...b})属于浅合并,后出现的对象同名属性会覆盖前面的。这不是原型链问题,但常被误认为“冲突”。
- 如果只是普通配置合并,覆盖就是预期行为,无需处理
- 需要深度合并(嵌套对象也递归合并)?得手写或引入工具库
- 想自定义规则(如数组拼接、数字相加、保留首次值)?必须遍历键,按类型分别判断并执行策略
方法重名是设计选择,不是 bug
子类原型上定义同名方法(如 render),会自然覆盖父类原型上的版本。是否合理,取决于你的意图:
- 想完全替换:直接在子原型上重写即可
- 想增强父逻辑(例如“校验后再提交”):在子方法中显式调用 Parent.prototype.submit.call(this)
- 想两个版本共存:改用语义化命名,比如 initBasic() 和 initWithAuth(),避免强行复用同一名称











