JavaScript 原型继承与属性访问规则详解
一、原型继承基本概念
JavaScript 使用原型继承(Prototypal Inheritance)作为其面向对象编程的基础,这与传统的基于类的继承有本质区别。
1. 原型链(Prototype Chain)
每个 JavaScript 对象都有一个内部链接指向另一个对象,称为它的原型(prototype)。当访问对象的属性时,如果对象自身没有该属性,就会沿着原型链向上查找。
function Animal(name) {this.name = name;
}Animal.prototype.speak = function() {console.log(`${this.name} makes a noise.`);
};const dog = new Animal('Dog');
dog.speak(); // 先在dog实例查找,然后在Animal.prototype查找
2. 关键对象和属性
__proto__
(现已标准化为Object.getPrototypeOf()
):对象的实际原型prototype
:构造函数特有的属性,用于设置新创建实例的原型constructor
:指向创建该对象的构造函数
二、属性访问规则
1. 属性查找机制
当访问对象属性时,JavaScript 引擎遵循以下顺序:
检查对象自身属性(包括可枚举和不可枚举属性)
如果未找到,沿着原型链向上查找
直到
Object.prototype
(所有对象的顶层原型,其__proto__
为null
)如果最终未找到,返回
undefined
const parent = { a: 1 };
const child = Object.create(parent);
child.b = 2;console.log(child.b); // 2 (自身属性)
console.log(child.a); // 1 (继承属性)
console.log(child.c); // undefined (不存在)
2. 属性遮蔽(Property Shadowing)
如果对象和它的原型有同名属性,对象自身的属性会遮蔽原型上的属性:
const proto = { value: 10 };
const obj = Object.create(proto);
obj.value = 20; // 遮蔽原型属性console.log(obj.value); // 20 (自身属性)
delete obj.value;
console.log(obj.value); // 10 (恢复访问原型属性)
三、原型继承的实现方式
1. 构造函数模式
function Person(name) {this.name = name;
}Person.prototype.greet = function() {console.log(`Hello, I'm ${this.name}`);
};const john = new Person('John');
john.greet(); // Hello, I'm John
2. Object.create()
const animal = {eat() {console.log(`${this.name} eats.`);}
};const rabbit = Object.create(animal, {name: { value: 'White Rabbit' }
});rabbit.eat(); // White Rabbit eats.
3. ES6 Class 语法糖
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a noise.`);}
}class Dog extends Animal {speak() {console.log(`${this.name} barks.`);}
}const d = new Dog('Rex');
d.speak(); // Rex barks.
四、属性访问的特殊情况
1. 不可写属性
如果原型属性被标记为 writable: false
,子对象无法修改该属性:
const proto = {};
Object.defineProperty(proto, 'value', {value: 10,writable: false
});const obj = Object.create(proto);
obj.value = 20; // 严格模式下会报错console.log(obj.value); // 10 (修改失败)
2. setter/getter 继承
const proto = {get value() {return this._value * 2;},set value(v) {this._value = v;}
};const obj = Object.create(proto);
obj.value = 10;
console.log(obj.value); // 20
五、原型相关方法
1. 检查原型关系
// 检查对象是否是另一个对象的原型
console.log(proto.isPrototypeOf(obj)); // true// 检查实例关系
console.log(obj instanceof Object); // true
2. 获取/设置原型
// 获取原型
const proto = Object.getPrototypeOf(obj);// 设置原型(性能差,不推荐)
Object.setPrototypeOf(obj, newProto);
六、最佳实践
不要直接修改内置对象原型(如
Array.prototype
)优先使用
Object.create()
而非__proto__
对于性能敏感代码,避免长原型链
考虑使用 ES6 Class 语法提高可读性
七、原型继承图示
实例对象 (obj) → 构造函数.prototype → Object.prototype → null↑ ↑|__proto__ |__proto__
JavaScript 的原型继承提供了极大的灵活性,理解其工作原理对于编写高效、可维护的代码至关重要。