原型链与继承
一、原型链
在 JavaScript 中,每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 或 Object.getPrototypeOf() 访问),该属性指向另一个对象。当访问一个对象的属性时,如果自身没有该属性,就会沿着 [[Prototype]] 链向上查找,直到找到该属性或到达 null 为止。这种对象之间的关联关系称为原型链。
- 每个构造函数都有一个
prototype属性,指向其原型对象。 - 通过构造函数
new创建的对象,其[[Prototype]]指向该构造函数的prototype对象。 - 原型对象本身也是一个普通对象,其
[[Prototype]]指向Object.prototype,最终Object.prototype的[[Prototype]]为null,形成链式结构。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
const p = new Person('Alice');
p.sayHi(); // 通过原型链找到 sayHi
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
二、ES5 实现继承(推荐寄生组合继承)
ES5 中实现继承的常用方式是寄生组合继承,它解决了组合继承中会调用两次父类构造函数的问题,且避免了在父类原型上添加无用属性。
javascript
// 父类
function Animal(name) {
this.name = name;
this.colors = ['black', 'white'];
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
// 子类
function Dog(name, breed) {
// 借用构造函数继承属性
Animal.call(this, name);
this.breed = breed;
}
// 寄生组合继承:原型继承 + 修复构造函数指向
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype); // 创建父类原型的副本
prototype.constructor = child; // 修复 constructor 指向
child.prototype = prototype;
}
inheritPrototype(Dog, Animal);
// 添加子类自己的方法
Dog.prototype.bark = function() {
console.log('Woof!');
};
// 测试
const dog = new Dog('Buddy', 'Golden');
dog.speak(); // Buddy makes a sound.
dog.bark(); // Woof!
console.log(dog.colors); // ['black', 'white']
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
要点:
- 属性继承:在子类构造函数中用
Parent.call(this)继承父类属性。 - 方法继承:通过
Object.create(parent.prototype)创建原型对象,并修复constructor指向,避免共享引用类型属性。 - 这样既保证了父类构造器只调用一次,又使原型链正确。
三、ES6 实现继承(class 语法)
ES6 引入了 class 和 extends 关键字,底层仍基于原型链,但语法更清晰、更易理解。
javascript
// 父类
class Animal {
constructor(name) {
this.name = name;
this.colors = ['black', 'white'];
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
// 子类
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数,相当于 Animal.call(this, name)
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
// 测试
const dog = new Dog('Buddy', 'Golden');
dog.speak(); // Buddy makes a sound.
dog.bark(); // Woof!
console.log(dog.colors); // ['black', 'white']
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
要点:
extends设置原型链:Dog.prototype.__proto__ === Animal.prototype。- 子类构造函数中必须调用
super()才能使用this,且super调用前不能引用this。 - 静态方法也可通过
extends继承(Dog.__proto__ === Animal)。 - 本质仍是原型链继承,只是语法糖。
四、继承的核心差异
| 方式 | 属性继承 | 方法继承 | 特点 |
|---|---|---|---|
| ES5 寄生组合 | Parent.call(this) | Object.create(Parent.prototype) | 需手动处理原型链和构造函数指向 |
| ES6 class | super() | extends + super | 语法简洁,自动处理原型链和构造函数 |
两种方式均能正确实现继承,ES6 更符合现代开发习惯,且保证了原型链的正确性。