原型链中的优先级规则
实例属性优先于原型属性
当对象自身的属性和原型链上的属性同名时,实例自身的属性优先。JavaScript 引擎查找属性时从对象自身开始,找到就停,不会继续往上找。
看一个经典面试题:
javascript
function Foo() {
this.a = function () {
console.log('2');
};
}
Foo.prototype.a = function () {
console.log('3');
};
Foo.a = function () {
console.log('4');
};
const obj = new Foo();
obj.a(); // 输出 2输出 2 而不是 3 或 4,原因在于三层定义各自的作用域:
this.a在构造函数中定义,是实例自身的属性。new Foo()创建对象时,this.a被赋值为一个函数,放在实例自身上。查找obj.a时在第一层就找到了,不会继续往原型上找。Foo.prototype.a定义在原型上,是所有实例共享的方法。如果实例自身没有a这个属性,查找才会走到这里。Foo.a是构造函数本身的静态方法,挂在Foo这个函数对象上,和obj实例没有关系——obj.a根本不会去找Foo.a。要调用它只能通过Foo.a()。
查找顺序的完整路径
总结一下属性查找的完整路径:
obj 自身属性 → obj.__proto__ (即 Foo.prototype) → Foo.prototype.__proto__ (即 Object.prototype) → null每一步的顺序是固定的:自身 → 原型 → 原型的原型 → null。这个机制保证了:
- 实例可以"覆盖"原型上的同名方法(方法重写)
- 所有对象默认继承
Object.prototype的方法(toString、hasOwnProperty等) - 原型链末端的
null是查找的终点,避免无限循环
静态方法 vs 原型方法 vs 实例方法
把三种方法放在一起对比:
javascript
function Order() {
// 实例方法——挂在对象自身上
this.getStatus = function () {
return 'pending';
};
}
// 原型方法——挂在 prototype 上,实例共享
Order.prototype.cancel = function () {
console.log('订单已取消');
};
// 静态方法——挂在构造函数上,和实例无关
Order.findAll = function () {
console.log('查询所有订单');
};
const order = new Order();
order.getStatus(); // 实例方法,自身属性,直接找到
order.cancel(); // 原型方法,自身找不到,沿原型链找到
Order.findAll(); // 静态方法,通过构造函数调用
// order.findAll(); // TypeError——实例上找不到静态方法这三种方法的区别不仅是写法位置不同,更决定了调用方式和适用场景:实例方法放每个对象独有的逻辑;原型方法放共享的行为;静态方法放不依赖实例状态的工具函数。