文章目录
- 前言
- 一、原型链 (Prototype Chain)
- 二、借用构造函数 (Constructor Stealing / Classical Inheritance)
- 三、组合式继承 (Combination Inheritance)
- 四、原型式继承 (Prototypal Inheritance)
- 五、寄生式继承 (Parasitic Inheritance)
- 六、ES6 类 (Class) 和 extends 关键字
- 结语
前言
JavaScript中的继承机制基于原型链,这与传统的面向对象语言(如Java或C++)有所不同。为了更详细地理解JavaScript中实现继承的方法,我们将深入探讨每一种方式,并提供更详细的代码示例和解释。
一、原型链 (Prototype Chain)
原理:通过将子类的原型设置为父类的实例来实现继承。这是最基础的继承方式。
优点:
- 简单直接。
- 子类可以访问父类的所有属性和方法。
缺点:
- 所有子类实例共享同一个原型实例,可能导致意外的行为,特别是当涉及到引用类型属性时。
- 无法传递参数给父类构造函数。
代码示例:
function SuperType() {this.property = true;
}SuperType.prototype.getSuperValue = function() {return this.property;
};function SubType() {}// 继承自 SuperType
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; // 修正构造函数指向
SubType.prototype.getSubValue = function () {return false;
};const instance = new SubType();
console.log(instance.getSuperValue()); // 输出: true
注意:在修改SubType.prototype
后,需要重新指定constructor
属性,以确保它正确指向SubType
。
二、借用构造函数 (Constructor Stealing / Classical Inheritance)
原理:在子类构造函数内部调用父类构造函数,并使用call()或apply()方法将this绑定到新创建的对象上来实现继承。
优点:
- 每个子类实例都有自己的一份父类属性副本,避免了引用类型的共享问题。
- 可以向父类构造函数传递参数。
缺点:
- 不能继承父类的原型方法,只能继承构造函数中的属性和方法。
- 如果父类构造函数中有复杂的初始化逻辑,可能会重复执行。
代码示例:
function SuperType(name) {this.name = name;this.colors = ["red", "blue", "green"];
}function SubType(name, age) {SuperType.call(this, name); // 继承 SuperType 的属性this.age = age;
}const instance1 = new SubType("Nicholas", 30);
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]const instance2 = new SubType("Greg", 25);
console.log(instance2.colors); // ["red", "blue", "green"]
三、组合式继承 (Combination Inheritance)
原理:结合原型链和借用构造函数的优点,先通过原型链继承原型上的方法,再通过借用构造函数继承实例属性。
优点:
- 解决了上述两种方法的问题,是最常用的继承模式之一。
- 可以有效避免多次调用父类构造函数的问题。
缺点:
- 构造函数会被调用两次,一次是在设置原型时,另一次是在创建子类实例时。
改进:可以通过Object.create()
来避免构造函数被调用两次的问题。
代码示例:
function SuperType(name) {this.name = name;this.colors = ["red", "blue", "green"];
}SuperType.prototype.sayName = function() {console.log(this.name);
};function SubType(name, age) {SuperType.call(this, name); // 第二次调用 SuperType()this.age = age;
}// 使用 Object.create 避免构造函数被调用两次
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function() {console.log(this.age);
};const instance = new SubType("Nicholas", 29);
instance.sayName(); // Nicholas
instance.sayAge(); // 29
四、原型式继承 (Prototypal Inheritance)
原理:利用Object.create()
方法可以更简洁地创建新对象并指定其原型。
优点:
- 不需要定义构造函数即可实现继承。
- 简化了对象的创建过程。
缺点:
- 与原型链相似,所有实例共享相同的属性。
代码示例:
const person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]
};const anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");const yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barry");console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barry"]
五、寄生式继承 (Parasitic Inheritance)
原理:基于原型式继承,但会增强对象,即在创建的新对象上添加一些新的属性或方法,然后返回这个新对象。
优点:
- 可以在不修改原始对象的情况下扩展功能。
缺点:
- 效率较低,因为每次都要创建一个新的对象。
代码示例:
function createAnother(original) {const clone = Object.create(original); // 通过调用函数创建一个新对象clone.sayHi = function() { // 以某种方式增强这个对象console.log("Hi");};return clone; // 返回这个对象
}const person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]
};const anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "Hi"
六、ES6 类 (Class) 和 extends 关键字
原理:ES6引入了类的概念,使得代码看起来更加面向对象,并且简化了继承的实现。
优点:
- 语法清晰,易于理解和使用。
- 支持静态方法和getter/setter等特性。
- 提供了更简洁的构造函数调用方式(
super()
)。
代码示例:
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a noise.`);}
}class Dog extends Animal {constructor(name) {super(name); // 调用父类的构造函数}speak() {console.log(`${this.name} barks.`);}
}const d = new Dog('Mitz');
d.speak(); // Mitz barks.
额外特性:
- 静态方法:可以直接在类上定义静态方法,不需要实例化类就可以调用。
- getter/setter:可以为属性定义存取器方法,以便控制属性的读写行为。
- 私有字段:通过#前缀可以定义私有字段,限制外部访问。
class MyClass {#privateField = 'private';getPrivateField() {return this.#privateField;}static myStaticMethod() {console.log('Called static method');}
}MyClass.myStaticMethod(); // Called static method
const instance = new MyClass();
console.log(instance.getPrivateField()); // private
结语
JavaScript提供了多种实现继承的方式,每种方式都有其独特的优势和局限性。随着ECMAScript标准的发展,推荐尽可能采用ES6的class和extends关键字来实现继承,因为它们不仅使代码更易读,而且也更容易维护。希望这篇文章能帮助你更好地理解JavaScript中不同的继承方式及其优缺点,并为你选择合适的实现方案提供指导。