什么是原型链继承
在JavaScript中,原型链继承是实现对象间继承关系的主要方式。每个JavaScript对象都有一个内部链接指向另一个对象(称为它的原型),而这个原型对象又有自己的原型,依此类推,直到某个对象的原型为null
为止。这种链式结构被称为原型链。
javascript
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
this.childName = 'child';
}
// 实现原型链继承
Child.prototype = new Parent();
const child = new Child();
child.sayName(); // 输出: parent
原型链继承的实现原理
-
构造函数、原型和实例的关系:
- 每个构造函数都有一个原型对象(
prototype
) - 原型对象包含一个指向构造函数的指针(
constructor
) - 实例包含一个指向原型对象的内部指针(
__proto__
)
- 每个构造函数都有一个原型对象(
-
继承的实现:
- 通过将子类的原型对象指向父类的实例来实现继承
- 这样就在子类和父类之间建立了原型链
-
查找机制:
- 当访问实例的属性/方法时,首先在实例自身查找
- 如果找不到,则沿着原型链向上查找
- 直到找到该属性/方法或到达原型链末端(
null
)
原型链继承的缺陷
- 引用类型值的共享问题:
javascript
function Parent() {
this.colors = ['red', 'blue'];
}
function Child() {}
Child.prototype = new Parent();
const child1 = new Child();
child1.colors.push('green');
const child2 = new Child();
console.log(child2.colors); // ['red', 'blue', 'green']
所有子类实例共享同一个父类实例的属性,当属性是引用类型时,一个实例修改会影响所有实例。
- 无法向父类构造函数传参:
在创建子类实例时,无法向父类构造函数传递参数,因为父类实例化是在设置子类原型时完成的。
- 原型链断裂问题:
javascript
Child.prototype = new Parent();
// 需要手动修复constructor指向
Child.prototype.constructor = Child;
如果不手动修复,子类实例的constructor会指向父类构造函数。
- 性能问题:
- 每次创建子类实例时都会执行一次父类构造函数
- 原型链过长会导致属性查找时间增加
改进方案
由于原型链继承存在上述缺陷,实践中通常会结合其他继承方式:
-
借用构造函数继承(经典继承):
- 在子类构造函数中调用父类构造函数
- 解决了引用类型共享和传参问题
- 但无法继承父类原型上的方法
-
组合继承:
- 结合原型链继承和借用构造函数继承
- 既能在实例上拥有独立属性,又能共享方法
javascript
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
- 寄生组合式继承:
- 目前最理想的继承方式
- 只调用一次父类构造函数
- 保持原型链不变
javascript
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
总结
原型链继承是JavaScript中最基本的继承方式,它通过建立对象间的原型链来实现属性和方法的共享。虽然简单直观,但也存在引用类型共享、无法传参等明显缺陷。在实际开发中,通常会采用组合继承或更优的寄生组合式继承来规避这些问题。理解原型链继承的原理和缺陷,有助于我们更好地掌握JavaScript面向对象编程的核心机制。