原理分析
- 原型链:JavaScript 中每个对象都有一个
[[Prototype]]
内部属性(可通过 __proto__
访问,标准方式是 Object.getPrototypeOf
),指向其原型对象。当访问对象的属性或方法时,如果对象本身没有该属性或方法,就会沿着原型链向上查找,直到找到或到达原型链顶端(null
)。
- 作用域:JavaScript 有函数作用域(ES6 引入块级作用域)。作用域决定了变量的可访问范围。当在某个作用域内查找变量时,会先在当前作用域查找,若未找到则向上级作用域查找,直到全局作用域。
- 赋值操作陷阱:
- 意外覆盖原型属性:如果在对象实例上直接对某个属性赋值,而该属性恰好存在于原型链上,就会在实例上创建一个新的属性,从而遮蔽原型上的同名属性。例如:
function Animal() {}
Animal.prototype.sound = 'generic sound';
let dog = new Animal();
dog.sound = 'woof'; // 这里在 dog 实例上创建了 sound 属性,遮蔽了原型上的 sound 属性
- 作用域链与原型链混淆:在函数内部,如果对对象属性赋值时,当前作用域存在同名变量,可能会导致误解。比如:
function test() {
let obj = {name: 'initial'};
function inner() {
let name = 'local';
obj.name = name; // 这里本意可能是修改 obj 的 name 属性,但容易因作用域问题导致错误
}
inner();
console.log(obj.name); // 输出 'local',可能与预期不符
}
通用策略
- 明确赋值目标:在对对象属性赋值前,先确认是要操作实例属性还是原型属性。如果要操作原型属性,可直接在原型对象上进行修改,避免在实例上意外创建同名属性。例如:
function Shape() {}
Shape.prototype.color = 'white';
// 修改原型属性
Shape.prototype.color = 'black';
let square = new Shape();
console.log(square.color); // 输出 'black'
- 使用
hasOwnProperty
检查:在对属性赋值前,使用 hasOwnProperty
方法检查对象本身是否已经有该属性,避免意外遮蔽原型属性。例如:
function Person() {}
Person.prototype.age = 0;
let john = new Person();
if (!john.hasOwnProperty('age')) {
john.age = 30;
}
console.log(john.age); // 输出 30
- 注意作用域:在函数内部对对象属性赋值时,确保变量名不会与作用域内其他变量冲突。尽量使用更具描述性的变量名,并且在必要时使用
this
来明确对象。例如:
function Car() {
this.model = 'default';
function setModel(newModel) {
this.model = newModel; // 使用 this 明确是 Car 实例的 model 属性
}
setModel.bind(this)('sports car');
console.log(this.model); // 输出'sports car'
}
let myCar = new Car();