1. 原型链继承
-
优点:简单易实现;父类方法可以被多个子类实例共享,节省内存。
-
缺点:
- 所有子类实例共享父类引用属性,一个子类实例的改变会影响其他所有子类实例
- 子类实例不能向父类构造函数传参。
-
例子:如果父类有一个数组属性,所有子类实例都会共享这个数组,一个实例对数组的修改会反映在所有实例上。
function Parent() { this.colors = ['red', 'blue', 'green']; } function Child() {} Child.prototype = new Parent(); var child1 = new Child(); child1.colors.push('black'); var child2 = new Child(); console.log(child2.colors); // ['red', 'blue', 'green', 'black']
-
子类实例不能向父类构造函数传参
function Parent(name) { this.name = name; } function Child() {} Child.prototype = new Parent(); var child = new Child("Child Name"); console.log(child.name); // 输出: undefined
2. 构造函数继承
- 优点:可以在子类构造函数中向父类传递参数;避免了引用类型的属性被所有实例共享。
- 缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法,无法实现函数复用。
-
例子:每个子类实例都会创建自己的
sayName
方法,导致无法共享同一个函数。function Parent(name) { this.name = name; this.sayName = function() { console.log(this.name); }; } function Child(name) { Parent.call(this, name); } var child1 = new Child('child1'); var child2 = new Child('child2'); console.log(child1.sayName === child2.sayName); // false
-
3. 组合继承
- 优点:结合了原型链和构造函数的优点,是JavaScript中最常用的继承模式。
- 缺点:调用了两次父类构造函数,可能会造成一定的性能问题。
-
例子:
Parent
构造函数被调用两次,一次是在创建Child.prototype
,另一次是在Child
构造函数中。function Parent(name) { this.name = name; } function Child(name, age) { Parent.call(this, name); // 第一次调用 Parent this.age = age; } Child.prototype = new Parent(); // 第二次调用 Parent
-
4. 寄生组合式继承
- 优点:可以创建一个增强的对象。
- 缺点:无法实现函数复用;与原型链继承一样,包含引用类型的属性值始终会被共享。
- 例子:
// 父类
function Animal(name) {
this.name = name;
this.colors = ['white', 'black', 'brown'];
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
// 子类
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 寄生组合式继承的核心函数
function inheritPrototype(childClass, parentClass) {
var prototype = Object.create(parentClass.prototype); // 创建对象
prototype.constructor = childClass; // 增强对象
childClass.prototype = prototype; // 指定对象
}
// 应用寄生组合式继承
inheritPrototype(Dog, Animal);
// 新增或重写子类方法
Dog.prototype.sayBreed = function() {
console.log(this.breed);
};
// 测试寄生组合式继承
var myDog = new Dog("Rex", "Golden Retriever");
myDog.sayName(); // 输出 "Rex"
myDog.sayBreed(); // 输出 "Golden Retriever"
在JavaScript中,指定 prototype.constructor = childClass
是重要的一步,因为它确保了继承过程中保持了构造函数(constructor)的正确指向。这个步骤对于理解和维护对象的原型链是非常关键的。
当你使用 Object.create(superType.prototype)
创建一个新对象时,这个新对象的原型是 superType.prototype
。这意味着新对象(即子类的原型对象)的 constructor
属性是继承自 superType.prototype
的,因此它指向的是 superType
而不是 childType
。这在逻辑上是不正确的,因为我们希望 childType
的实例的构造器(constructor)指向 childType
本身。
通过设置 prototype.constructor = childClass
,我们确保了:
- 正确的构造函数引用:任何创建自
childClass
的实例都应该引用childClass
作为其构造函数。 - 保持原型链的完整性:这有助于在继承链中保持清晰的结构,便于理解和调试。
以下是一个简化的例子,展示了为什么需要这个步骤:
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
console.log(Child.prototype.constructor === Parent); // true
// 在这一步,Child的原型的构造函数还指向Parent
Child.prototype.constructor = Child;
console.log(Child.prototype.constructor === Child); // true
// 现在我们更正了constructor属性,使其指向Child
在这个例子中,我们首先创建了一个新的对象,它的原型是 Parent.prototype
,但这使得 Child.prototype.constructor
错误地指向 Parent
。然后,我们将 Child.prototype.constructor
更正为 Child
,以反映实际的继承关系。这样,任何 Child
的实例都会正确地将其 constructor
属性指向 Child
。