JavaScript 继承机制详解:从原型链到类式继承的演进
本文深入解析 JavaScript 中复杂的继承机制,对比基于类的继承与基于原型链的两种核心实现方式。详细阐述原型链继承中引用类型共享导致的数据不一致问题,以及类式继承通过 call 方法解决参数传递和属性隔离的方案。通过代码示例图解原理,帮助开发者掌握稳定且高效的 JS 继承模式。
JavaScript 继承概述
1、继承
在 JavaScript 中,继承是一个相对复杂的话题,其复杂程度远超其他大多数面向对象语言。在多数面向对象语言里,继承一个类通常只需使用一个关键字即可完成。然而,JavaScript 不同,要实现公用成员的传承,需要采取一系列特定的措施。此外,JavaScript 属于少数采用原型式继承机制的语言之一。得益于这种语言的灵活性,开发者既可以使用标准的基于类的继承方式,也可以采用更为微妙且灵活的原型式继承。
继承的必要性与设计原则
2、为什么需要继承?
通常在设计类时,我们的目标是减少重复代码并尽可能降低对象间的耦合度。使用继承符合这一设计原则。借助继承机制,可以在现有类的基础上进行扩展,充分利用其已有的方法,同时使后续的设计修改更加便捷。例如,若需让多个类都具备按特定格式输出结构的 toString() 方法,虽然可以通过复制粘贴将代码添加到每个类中,但这会导致维护困难:一旦需要修改该方法的行为,就必须逐一更新所有类。反之,如果提供一个 ToStringProvider 基类,并让其他类继承它,则 toString 方法只需在单一位置声明即可。
让一个类继承另一个类可能会导致二者产生强耦合,即一个类依赖于另一个类的内部实现细节。我们将讨论一些有助于避免这种问题的技术,其中包括利用掺元类(mixin)为其他类提供方法的技术。
基于类的继承实现与缺陷
3、基于类的继承
下面看下面的代码:
<script type="text/javascript">
function Person(name, age)
{
this.name = name;
this.age = age;
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age);
}
function Student(no)
{
this.no = no;
}
/**
* Student的prototype指向Person的对象
*/</span>
Student.prototype = new Person();
var stu1 = new Student("0001");
stu1.name = '张三';
stu1.age = '11';
console.log(stu1.no);
stu1.say();
</script>
输出结果: 张三 , 11 可以看到 Student 成功集成了 Person,并且拥有了 Person 的 say 方法,核心代码其实就是一句 Student.prototype = new Person();,下面通过图解来说明原理:

将 Student.prototype 指向 new Person() , new Person的_proto_又指向Person Prototype;这样完成了整个继承。
但是这种方式存在问题:
问题1:当父类存在引用类型变量时,造成数据不一致,下面我们给Person添加一个hobbies属性,类型为数组。
<script type="text/javascript">
/**
* 存在问题
* 1、无法在Student的构造方法中传递参数用于父类的构造方法
* 2、对于引用类型变量,造成数据不一致
*/
function Person(name, age)
{
this.name = name;
this.age = age;
this.hobbies = [] ;
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age +" , " +this.hobbies);
}
function Student(no)
{
this.no = no;
}
Student.prototype = new Person();
var stu1 = new Student("0001");
stu1.name = '张三';
stu1.age = '11';
stu1.hobbies.push("soccer");
stu1.say();
var stu2 = new Student("0002");
stu2.name = '李四';
stu2.age = '12';
stu2.hobbies.push("girl");
stu2.say();
</script>
输出结果: 张三 , 11 , soccer 李四 , 12 , soccer,girl 可以看出,李四的hobbies应该只有girl,但是上面的代码让所有对象共享了hobbies属性。 上述的继承方式还存在一个问题:
问题2:在Student的构造方法中,无法使用new Student(“00001” , “张三” , 12) ;创建对象,并初始化name和age属性,必须stu.name, stu.age进行赋值
为了解决上述问题,对上述代码进行修改:
<script type="text/javascript">
function Person(name, age)
{
this.name = name;
this.age = age;
this.hobbies = [];
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age +" , " + this.hobbies);
}
function Student(name, age, no)
{
/**
* 使用call方法,第一个参数为上下文;
* 有点类似Java中的super(name,age)的感觉
*/
Person.call(this, name, age);
this.no = no;
}
Student.prototype = new Person();
var stu1 = new Student("0001","张三",11);
stu1.hobbies.push("soccer");
stu1.say();
var stu2 = new Student("0002","李四",12);
stu2.hobbies.push("cangjin");
stu2.hobbies.push("basketball");
stu2.say();
</script>
输出: 0001 , 张三 , soccer 0002 , 李四 , cangjin,basketball 在Student的构造方法中使用了Person.call(this,name,age)感觉就像super(name,age)【call的第一个参数为上下文】;并且成功解决了对引用属性的共享问题,完美解决。
基于原型链的继承方式
4、基于原型链的继承
<script type="text/javascript">
/**
* 基于原型链的集成中都是对象
* 存在问题:
* 1、对于引用类型变量,造成数据不一致
*/
var Person = {
name: "人",
age: 0,
hobbies: [],
say: function ()
{
console.log(this.name + " , " + this.age + " , " + this.hobbies);
}
}
;
var Student = clone(Person);
Student.no ="";
Student.sayHello = function()
{
console.log(this.name +"hello ") ;
}
var stu1 = clone(Student);
stu1.name = "zhangsan";
stu1.age = 12;
stu1.hobbies.push("Java");
stu1.say();
var stu2 = clone(Student);
stu2.name = "lisi";
stu2.age = 13;
stu2.hobbies.push("Javascript");
stu2.say();
/**
* 返回一个prototype执行obj的一个对象
* @param obj
* @returns {F}
*/
function clone(obj)
{
var F = function ()
{
};
F.prototype = obj;
return new F();
}
</script>
输出: zhangsan , 12 , Java lisi , 13 , Java,Javascript 可以看出同样存在引用属性不一致的问题,并且整个操作全部基于对象,给人的感觉不是很好,下面通过图解解释下原理:

对象间通过一个clone函数,不断的返回一个新的对象,且prototype执行传入的对象,整个继承过程其实就是_proto_不断的指向,形成一个链,所以叫做原型链。
好了,已经介绍完了,js的两种集成的方式,最好使用的还是通过类的继承,比较稳定。
以上就是关于继承知识点的相关内容介绍,希望对大家的学习有所帮助。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
