JavaScript 八种继承模式详解与对比
本文系统梳理JavaScript中八种常见的继承实现方式,包括原型链、构造函数、组合、原型式、Object.create、寄生式、寄生组合式及ES6类继承。深入分析各模式的实现原理、优缺点及适用场景,重点解析组合继承的性能开销与寄生组合式优化的核心逻辑,帮助开发者根据实际需求选择最优继承方案。
1. 原型链继承
原型链继承是JavaScript中最基础的继承机制。每个对象都拥有一个指向其原型对象的指针,通过这条原型链,属性和方法可以沿着对象链向下传递。在实现上,将子构造函数的原型对象(prototype)指向父构造函数的一个实例,从而使得子对象能够访问父对象原型链上的属性和方法。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child() {
this.name = 'Child';
}
Child.prototype = new Parent();
var child = new Child();
child.sayHello(); // Hello
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child() {
this.name = 'Child';
}
Child.prototype = new Parent();
var child = new Child();
child.sayHello(); // Hello
在此示例中,Child的原型被设置为Parent的一个实例。当调用child.sayHello()时,JavaScript引擎会先在子对象自身查找sayHello方法,若未找到,则沿原型链向上查找并执行父对象原型上的该方法。需要注意的是,原型链继承存在一个显著缺陷:所有子实例共享同一个原型对象,因此对原型属性的修改会直接影响所有子对象。
2. 构造函数继承(经典继承)
构造函数继承的核心在于子构造函数内部调用父构造函数。具体实现时,利用**call()或apply()**方法将父构造函数的执行上下文(this)绑定到子对象上,从而完成属性的继承。
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
var child = new Child('Child');
console.log(child.name); // Child
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
var child = new Child('Child');
console.log(child.name); // Child
示例中,我们在Child构造函数内部调用Parent构造函数,并传入子对象所需的特定参数。这种方式的局限性在于,它仅能继承父构造函数的实例属性,而无法继承父构造函数原型对象(prototype)上定义的方法。
3. 组合继承
组合继承融合了原型链继承与构造函数继承的优势,既保留了父构造函数的实例属性,又复用了原型对象上的方法。其实现策略是:通过调用父构造函数来继承属性,同时将子构造函数的原型指向父构造函数的实例以继承方法。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
var child = new Child('Child');
child.sayHello(); // Hello
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
var child = new Child('Child');
child.sayHello(); // Hello
具体代码中,通过**Parent.call(this, name)**继承父构造函数的属性,并通过**Child.prototype = new Parent()**将子原型指向父实例以实现方法继承。组合继承的优点在于属性与方法皆可继承;然而,其缺点是在创建子对象时会调用两次父构造函数:一次在设置原型时,另一次在创建子实例时,这会产生不必要的性能开销。
4. 原型式继承
原型式继承借助一个临时构造函数和**Object.create()**方法来实现对象的继承关系。
var parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello');
}
};
var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello
var parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello');
}
};
var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello
示例中,我们定义了一个parent对象,随后使用**Object.create()**创建新对象child,并将其原型指向parent,从而实现继承。该模式的本质是创建一个新对象,使其原型指向另一个已有对象。这种方式虽能实现属性和方法的继承,但无法像构造函数那样传递参数。
5. Object.create()方法
Object.create()的作用是将现有对象的属性挂载到新对象的原型上,新对象本身为空。
ECMAScript 5规范通过引入Object.create()方法规范化了原型式继承。该方法接收两个参数:第一个是作为新对象原型的对象,第二个是可选的、用于定义新对象额外属性的对象。当仅传入一个参数时,Object.create()的行为与前述临时构造函数方法A的效果一致。
let person = {
name: 'mjy',
age: 19,
hoby: ['唱', '跳'],
showName() {
console.log('my name is: ', this.name)
}
}
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']let person = {
name: 'mjy',
age: 19,
hoby: ['唱', '跳'],
showName() {
console.log('my name is: ', this.name)
}
}
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']
优点:无需单独创建构造函数。
缺点:若属性中包含引用值,这些引用会在相关对象间共享;此外,子类实例无法向父类传递参数。
6. 寄生式继承
寄生式继承的思路与原型式继承及工厂模式类似。其核心是创建一个仅用于封装继承过程的函数,该函数内部以某种方式增强对象,最后返回该对象,仿佛它完成了所有工作。
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function createAnother(obj) {
let clone = objectCopy(obj);
clone.showName = function () {
console.log('my name is:', this.name);
};
return clone;
}
let person = {
name: "mjy",
age: 18,
hoby: ['唱', '跳']
}
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function createAnother(obj) {
let clone = objectCopy(obj);
clone.showName = function () {
console.log('my name is:', this.name);
};
return clone;
}
let person = {
name: "mjy",
age: 18,
hoby: ['唱', '跳']
}
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']
优点:代码写法简单,无需单独创建构造函数。
缺点:通过寄生式继承为对象添加函数会导致函数难以重用。由于无法做到函数复用,其效率低于构造函数模式。
7. 寄生组合式继承
前文提到的组合继承是经典的常用模式,但其最大问题在于无论何种情况都会调用两次父类构造函数:一次在创建子类型原型时,另一次在子类型构造函数内部。寄生组合式继承旨在降低父类构造函数的调用开销。
该模式通过借用构造函数继承属性,并通过原型链的混成形式继承方法。其本质是利用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype);
prototype.constructor = child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.hoby = ['唱', '跳']
}
Parent.prototype.showName = function () {
console.log('my name is:', this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
console.log('my age is:', this.age);
}
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype);
prototype.constructor = child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.hoby = ['唱', '跳']
}
Parent.prototype.showName = function () {
console.log('my name is:', this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
console.log('my age is:', this.age);
}
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']
优点:仅调用一次父构造函数,从而避免了在子原型上创建不必要且多余的属性;同时保持了原型链的完整性。
缺点:代码实现相对复杂。
8. ES6类继承
ES6引入了类的概念,通过class关键字和extends关键字可以实现类的继承。
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello');
}
}
class Child extends Parent {
constructor(name) {
super(name);
}
}
const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello');
}
}
class Child extends Parent {
constructor(name) {
super(name);
}
}
const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello
示例中,我们定义了一个Parent类,利用extends关键字实现子类Child对父类的继承。子类使用super关键字调用父类构造函数,并可访问父类的属性和方法。ES6类继承提供了更加语法简洁且符合面向对象编程习惯的继承方式。
以上总结了JavaScript中常见的八种继承方式,每种方式均有其特点与适用场景。开发者可根据具体需求选择最适合的继承模式来构建对象间的关系。
总结
本文介绍了JS中常见的8种继承方法。更多相关JS继承方法内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28
