在JavaScript的对象体系中,原型链是实现继承的核心机制。所谓寄生继承,并不是语言内置的关键字,而是一种借助中间函数“寄生”式地复制父类原型、再挂载到子类原型上的编码技巧。它的出现主要是为了解决单纯使用原型继承时,子类原型直接等于父类实例所带来的引用属性共享和多余构造调用问题。

一、为什么需要寄生继承
最基础的原型继承写法通常是让子类的原型指向父类的一个实例,例如把Child.prototype = new Parent()。这种做法会让子类原型上携带父类构造函数中定义的实例属性,如果这些属性是引用类型,所有子类实例就会共享同一份数据,修改一个会影响其他实例。此外,创建父类实例时父类构造函数会被执行,可能带来不必要的副作用。
寄生继承通过引入一个空的中间函数,把父类原型“借”过来,避免直接实例化父类。它的本质是构建一个干净的中间原型对象,只承载父类原型上的方法,而不包含父类构造时产生的实例成员。这样子类在建立原型链时更加纯粹,也更容易控制和调试。
二、寄生继承的核心实现步骤
实现寄生继承的关键,是先定义一个临时构造函数,将其原型设置为父类的原型,然后通过new该临时函数得到一个“干净”的对象,最后把这个对象作为子类的原型。下面是一段标准实现的代码示例:
// 父类构造函数
function Parent(name) {
this.name = name;
this.hobbies = [];
}
// 父类原型方法
Parent.prototype.sayName = function() {
return '我的名字是' + this.name;
};
// 寄生继承核心函数
function inheritPrototype(Child, Parent) {
// 创建中间函数,避免直接调用Parent构造函数
var Temp = function() {};
Temp.prototype = Parent.prototype;
// 得到仅包含父类原型方法的干净对象
var proto = new Temp();
// 修复构造函数指向
proto.constructor = Child;
Child.prototype = proto;
}
// 子类构造函数
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 执行寄生继承
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
return '我今年' + this.age + '岁';
};
var c1 = new Child('小明', 18);
var c2 = new Child('小红', 20);
c1.hobbies.push('篮球');
console.log(c1.hobbies); // ["篮球"]
console.log(c2.hobbies); // []
console.log(c1.sayName()); // 我的名字是小明
console.log(c1 instanceof Parent); // true
上面的inheritPrototype函数就是寄生继承的封装。注意其中Temp函数本身没有任何逻辑,只是作为一个跳板。通过new Temp()得到的proto对象,其原型链指向Parent.prototype,但自身并没有name或hobbies这类实例属性。
同时,我们在赋值后手动把proto.constructor指回Child,否则子类实例的构造函数会错误地指向Parent。这一步对于后续使用instanceof或依赖constructor判断类型的场景非常重要。
三、寄生继承与原型继承的对比
为了更直观地看出差异,我们可以用表格来对比两种常见写法在原型链结构上的不同:
| 继承方式 | 子类原型来源 | 是否执行父类构造 | 引用属性共享风险 |
|---|---|---|---|
| 直接原型继承 | new Parent()实例 | 是 | 高 |
| 寄生继承 | new Temp()干净对象 | 否 | 低 |
从表中可以看出,寄生继承在保持原型链连通(子类实例仍是父类实例)的同时,规避了直接原型继承的两大隐患。它也被广泛应用于早期JavaScript类库中对“优雅继承”的模拟,是组合继承优化版的重要基础。
不过寄生继承也并非万能。如果父类原型上有通过Object.defineProperty定义的不可枚举属性,部分老旧环境在复制原型时可能需要额外处理;现代开发中也可以直接使用Object.create(Parent.prototype)来替代手写中间函数,其底层思想与寄生继承完全一致。
四、使用Object.create的简化写法
在支持ES5的环境下,寄生继承可以被大幅简化。因为Object.create本身就能基于指定原型创建一个新对象,不需要我们再定义临时函数。示例如下:
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 简化版寄生继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
var child = new Child('张三', 25);
console.log(child.getName()); // 张三
console.log(child.getAge()); // 25
这种写法语义更清晰,也更容易被其他开发者理解。它依然遵循“只继承原型方法、不继承实例属性”的寄生原则,是当下手写继承逻辑时推荐的方式。
总结来说,寄生继承是一种通过中间对象隔离父类构造函数副作用、实现原型方法安全复用的经典方案。理解它有助于深入掌握JavaScript原型链运作方式,也为后续学习ES6的class语法背后的继承机制打下扎实基础。
JavaScript原型链寄生继承修改时间:2026-08-09 16:33:31