JavaScript 对象继承
JavaScript 对象的继承和基于类的继承(如 Java、C++)有本质区别。JavaScript 采用原型继承(Prototypal Inheritance),这是一种基于原型链(Prototype Chain)的继承机制。
1. 原型链继承
这是最基本的继承方式,通过将子类的原型指向父类的实例来实现。
javascript
// 父类构造函数
function Animal(name) {
this.name = name;
}
// 父类方法
Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
// 子类构造函数
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 关键步骤:将 Dog.prototype 的原型指向 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向
// 子类特有方法
Dog.prototype.bark = function() {
console.log('Woof!');
};
// 使用示例
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.sayName(); // My name is Buddy
myDog.bark(); // Woof!
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
**优点:**实现简单,能继承父类原型上的属性和方法。
**缺点:**所有子类实例共享同一个父类实例的引用属性,修改一个会影响所有。
2. 构造函数继承
在子类构造函数中调用父类构造函数,使用 call 或 apply 方法。
javascript
function Animal(name) {
this.name = name;
this.colors = ['black', 'white'];
}
function Dog(name, breed) {
Animal.call(this, name); // 继承实例属性
this.breed = breed;
}
const dog1 = new Dog('Max', 'Husky');
dog1.colors.push('brown');
const dog2 = new Dog('Bella', 'Poodle');
console.log(dog2.colors); // ['black', 'white'],不受 dog1 影响
**优点:**解决了引用属性共享的问题,每个实例都有独立的属性副本。
**缺点:**无法继承父类原型上的方法。
3. 组合继承(最常用)
结合原型链继承和构造函数继承,既继承方法又继承属性。
javascript
function Animal(name) {
this.name = name;
this.colors = ['black', 'white'];
}
Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
function Dog(name, breed) {
Animal.call(this, name); // 第二次调用父类构造函数
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 第一次调用父类构造函数
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog1 = new Dog('Charlie', 'Beagle');
const dog2 = new Dog('Lucy', 'Dachshund');
dog1.colors.push('brown');
console.log(dog1.colors); // ['black', 'white', 'brown']
console.log(dog2.colors); // ['black', 'white']
dog1.sayName(); // My name is Charlie
dog1.bark(); // Woof!
**优点:**融合了两种继承方式的优点,是 JavaScript 中最常用的继承模式。
**缺点:**父类构造函数被调用了两次。
4. 原型式继承
基于已有对象创建新对象,使用 Object.create()。
javascript
const animal = {
name: 'Animal',
sayName: function() {
console.log('My name is ' + this.name);
}
};
const dog = Object.create(animal, {
breed: {
value: 'Golden Retriever',
writable: true,
enumerable: true
},
bark: {
value: function() {
console.log('Woof!');
},
writable: true,
enumerable: true
}
});
dog.name = 'Buddy';
dog.sayName(); // My name is Buddy
dog.bark(); // Woof!
**适用场景:**不需要创建构造函数,只想让一个对象继承另一个对象。
5. 寄生式继承
在原型式继承的基础上增强对象,添加额外的方法或属性。
javascript
function createDog(original) {
const clone = Object.create(original);
clone.bark = function() {
console.log('Woof!');
};
return clone;
}
const animal = {
name: 'Animal',
sayName: function() {
console.log('My name is ' + this.name);
}
};
const dog = createDog(animal);
dog.name = 'Max';
dog.sayName(); // My name is Max
dog.bark(); // Woof!
6. 寄生组合式继承(最优方案)
ES5 时代最理想的继承方式,解决了组合继承中父类构造函数被调用两次的问题。
javascript
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function Animal(name) {
this.name = name;
this.colors = ['black', 'white'];
}
Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
function Dog(name, breed) {
Animal.call(this, name); // 只调用一次父类构造函数
this.breed = breed;
}
// 使用寄生组合式继承
inheritPrototype(Dog, Animal);
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Rocky', 'Bulldog');
dog.sayName(); // My name is Rocky
dog.bark(); // Woof!
7. ES6 Class 继承
ES6 引入了 class 语法糖,底层仍然是基于原型的继承。
javascript
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类的 constructor
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.sayName(); // My name is Buddy
myDog.bark(); // Woof!
注意: ES6 的 class 只是语法糖,JavaScript 的继承机制仍然是基于原型的。
总结对比
| 继承方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原型链继承 | 简单,能继承原型方法 | 引用属性共享,无法向父类传参 | 简单继承场景 |
| 构造函数继承 | 可向父类传参,引用属性独立 | 无法继承原型方法 | 需要独立属性副本的场景 |
| 组合继承 | 融合两者优点 | 父类构造函数调用两次 | 通用场景(ES5) |
| 寄生组合式继承 | 最优方案,效率高 | 实现稍复杂 | 追求性能的场景 |
| ES6 Class | 语法简洁,易读易写 | 本质仍是原型继承 | 现代 JavaScript 开发 |
在实际开发中,推荐使用 ES6 的 class 语法,它更清晰、更易维护。理解底层的原型继承机制有助于深入掌握 JavaScript 面向对象编程。