JavaScript 继承模式详解与代码示例
JavaScript 提供了多种实现继承的方式,每种方式都有其优缺点。以下将详细介绍几种常见的继承模式,并提供相应的代码示例。
1. 构造函数继承(借用构造函数)
此方法通过在子类构造函数中调用父类构造函数来继承父类的属性。它能够继承父类的实例属性,但无法继承父类原型上的方法。
function Animal(name) {
this.name = name;
this.species = "哺乳动物";
}
Animal.prototype.move = function() {
console.log("正在移动...");
};
function Dog(breed, name) {
Animal.call(this, name); // 调用 Animal 构造函数,继承实例属性
this.breed = breed;
}
Dog.prototype.bark = function() {
console.log("汪汪叫!");
};
var dog1 = new Dog("金毛", "旺财");
console.log(dog1);
// 输出: Dog { name: '旺财', species: '哺乳动物', breed: '金毛' }
// dog1.move() 会报错,因为 move 是 Animal.prototype 上的方法
2. 原型继承
这种方式将子类的原型指向父类的实例。它能够继承父类原型上的方法,但存在一个问题:子类的所有实例会共享同一个父类实例,导致原型污染。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("你好,我是 " + this.name);
};
function Student(id) {
this.id = id;
}
Student.prototype = new Person(); // 将 Student 的原型指向 Person 的实例
Student.prototype.study = function() {
console.log(this.name + " 正在学习。");
};
var student1 = new Student("S001");
student1.name = "小明"; // 给 student1 实例设置 name 属性
student1.sayHello(); // 输出: 你好,我是 小明
student1.study(); // 输出: 小明 正在学习。
var student2 = new Student("S002");
// student2.name 此时也是 undefined,因为 Person 实例的 name 属性被 student1 占用
// student2.sayHello(); // 输出: 你好,我是 undefined
// 错误示例:直接将原型指向父类原型,会导致 constructor 指向错误
// Student.prototype = Person.prototype;
3. 组合继承(原型链 + 构造函数)
这是最常用且推荐的继承方式之一。它结合了构造函数继承和原型继承的优点:既能继承父类的实例属性,又能继承父类原型上的方法,并且子类实例之间互不影响。
function Vehicle(brand) {
this.brand = brand;
this.wheels = 4;
}
Vehicle.prototype.start = function() {
console.log(this.brand + " 正在启动...");
};
function Car(model, brand) {
Vehicle.call(this, brand); // 继承 Vehicle 的实例属性
this.model = model;
}
// 继承 Vehicle 的原型方法,并重置 constructor
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.drive = function() {
console.log(this.brand + " 的 " + this.model + " 正在行驶...");
};
var myCar = new Car("Tesla Model 3", "Tesla");
console.log(myCar);
// 输出: Car { brand: 'Tesla', wheels: 4, model: 'Tesla Model 3' }
myCar.start(); // 输出: Tesla 正在启动...
myCar.drive(); // 输出: Tesla 的 Tesla Model 3 正在行驶...
4. 寄生组合继承
这是组合继承的优化版本,它避免了在子类原型上创建不必要的父类实例。通过创建一个中间函数,将其原型指向父类原型,然后让子类原型指向该中间函数的实例。
function Electronics(name) {
this.name = name;
}
Electronics.prototype.powerOn = function() {
console.log(this.name + " 已开机。");
};
function Smartphone(model, name) {
Electronics.call(this, name); // 继承实例属性
this.model = model;
}
// 寄生组合继承
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); // 创建父类原型的新实例
prototype.constructor = subType; // 修正 constructor 指向
subType.prototype = prototype; // 修正子类原型
}
inheritPrototype(Smartphone, Electronics);
Smartphone.prototype.call = function() {
console.log(this.model + " 正在拨打电话...");
};
var myPhone = new Smartphone("iPhone 14", "Apple");
console.log(myPhone);
// 输出: Smartphone { name: 'Apple', model: 'iPhone 14' }
myPhone.powerOn(); // 输出: Apple 已开机。
myPhone.call(); // 输出: iPhone 14 正在拨打电话...
5. ES6 Класс 继承
ES6 引入了 `class` 关键字,提供了更简洁、更面向对象的语法糖来实现继承。`extends` 关键字用于继承,`super` 关键字用于调用父类的方法(包括构造函数)。
class Animal {
constructor(name) {
this.name = name;
this.type = "动物";
}
eat() {
console.log(this.name + " 正在进食。");
}
}
class Mammal extends Animal {
constructor(name, species) {
super(name); // 调用父类构造函数
this.species = species;
}
sleep() {
console.log(this.name + " 正在睡觉。");
}
}
var lion = new Mammal("狮子王", "猫科");
console.log(lion);
// 输出: Mammal { name: '狮子王', type: '动物', species: '猫科' }
lion.eat(); // 输出: 狮子王 正在进食。
lion.sleep(); // 输出: 狮子王 正在睡觉。