深入解析JavaScript的六种继承模式:原理、对比与实战
本文系统梳理JavaScript中六种核心继承模式,包括类式继承、原型继承及组合继承等。通过对比构造函数与原型链在参数传递、内存复用及引用类型共享上的差异,深入解析各类继承的适用场景与局限性,帮助开发者根据实际需求选择最优方案。
类式继承与原型继承基础
类式继承(构造函数)
JavaScript语言本身并未原生定义“类”的概念,所谓的类是通过函数模拟实现的。当使用new关键字时,这种模拟更接近其他面向对象语言中的类定义。类式继承的核心机制是在子类的函数内部调用父类的构造函数,从而让子类实例获得父类的属性和方法。这一过程主要依赖call和apply方法来改变this的指向,使子类实例能够拥有父类的各项属性。
var father = function() {
this.age = 52;
this.say = function() {
alert('hello i am '+ this.name ' and i am '+this.age + 'years old');
}
}
var child = function() {
this.name = 'bill';
father.call(this);
}
var man = new child();
man.say();
原型继承
原型继承是开发中极为常见的模式。与类式继承不同,它不直接在对象本身定义方法,而是将方法定义在对象的原型(prototype)上。每个对象都拥有一个原型,在浏览器环境中通常表现为隐藏的__proto__属性,部分现代浏览器允许修改该属性。例如在Zepto库中,通过将zepto.fn对象添加到空数组的__proto__属性中,使该数组具备Zepto对象的所有方法。当对象调用某个方法时,引擎会先在当前对象查找,若未找到则沿原型链向上查找,直至找到目标方法或原型指向null为止。原型继承的本质是将父对象的方法赋值给子类的原型,而子类的构造函数本身并不包含这些方法和属性。
var father = function() {
}
father.prototype.a = function() {
}
var child = function(){}
//开始继承
child.prototype = new father();
var man = new child();
man.a();
代码示例展示了原型继承的实现方式。通过在浏览器控制台打印man对象,可以直观查看各层原型的继承关系。

从打印结果可见,继承层级为child -> object(father的实例) -> father。child通过中间层继承了father原型上的内容。之所以需要object这一中间层,而非直接执行child.prototype = father.prototype,是因为直接赋值会导致child和father失去区别。原型对象中有一个constructor属性,默认指向构造函数。若直接赋值,constructor的指向将变得模糊。因此,必须通过中间层确保child和father作为独立对象存在,并修正constructor的指向。
类式与原型继承对比分析
类式继承和原型继承的对比
构造函数(类)式继承
首先,构造函数继承的方式使得所有方法都存在于父对象实例中。每次创建子类实例时,都会将函数重新保存在内存中,这必然导致性能开销。
其次,类式继承具有不可变性,难以复用。在运行时无法动态修改或添加新方法,这种机制较为僵化,因此在实践中很少单独使用。
原型继承
优点:
原型链具备可变性:原型链上的父类可以被替换或扩展。
可以通过修改原型链接来动态调整子类的行为。此外,类式继承不支持多重继承,而原型继承只需编写相应的extend逻辑即可对对象进行扩展。
然而,原型链继承也存在两个主要问题。
第一,包含引用类型值(如数组、对象)的原型属性会被所有实例共享。例如,执行sub1.arr.push(2)时,引擎在实例中未找到arr属性,便沿原型链向上查找至sub1的原型对象,发现arr属性后将其末尾插入2。由于sub2共享同一原型,sub2.arr也会随之变化。
第二,在创建子类型实例时,无法向超类型的构造函数传递参数。更准确地说,无法在不影响所有对象实例的前提下,单独给超类型构造函数传递特定参数。因此,实践中也很少单独使用原型链继承。
function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
// ...
}
Sub.prototype = new Super(); // 核心
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1, 2
总结:
类式继承在实例化时支持向父类传递参数,但无法实现函数复用(父类不可变,每次实例化都会将父类内容保存在内存中)。
原型继承在实例化时不支持向父类传递参数,但可以实现函数复用(原型链可改变,父类可替换扩展,父类不保存在内存中而是沿原型链查找)。然而,其结果是原型属性会被所有实例共享,尤其影响引用类型值。
组合继承模式详解
组合继承(最常用)
组合继承模式结合了原型链和借用构造函数两种技术,旨在发挥两者的优势。
其核心思路是利用原型链实现对原型属性和方法的继承,同时通过借用构造函数实现对实例属性的继承。
function SuperType(name){
this.name = name;
this.numbers = [1,2,3];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new SubType('aaa',21);
instance1.numbers.push(666);
console.log(instance1.numbers);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType('bbb',22);
console.log(instance2.numbers);
instance2.sayName();
instance2.sayAge();
具体实现是将实例函数放置在原型对象上,通过Sub.prototype = new Super()语句继承父类函数,从而实现函数的复用。
同时保留借用构造函数的优点,通过Super.call(this)语句继承父类的基本属性和引用属性,从而实现参数的传递。
优缺点
优点:
- 支持参数传递。
- 函数可以复用。
- 不存在引用属性共享问题(即每个实例拥有独立的引用属性副本,如图纸般独立)。
缺点:
存在一个微小瑕疵:子类原型上会保留一份多余的父类实例属性。这是因为父类构造函数被调用了两次,生成了两份实例,而子类实例上的那一份屏蔽了子类原型上的同名属性。这确实造成了内存浪费,虽然比前述情况有所改善,但仍是一个缺陷。
以上所述是小编给大家介绍的JavaScript的六种继承方式,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
