JavaScript ES5 实现继承的4种常用方法详解
本文详细解析 JavaScript ES5 中实现继承的四种常用方式。从基于原型链的原型继承,到通过构造函数绑定 this 的构造函数继承,再到结合两者的组合式继承,最后引出最优解——寄生组合式继承。文章对比了各方法的优缺点,重点阐述如何解决引用类型共享及构造函数重复执行的问题,帮助开发者深入理解 JS 面向对象编程的核心机制。
前言
继承是面向对象软件开发中的核心概念。若类别 B “继承自”类别 A,则 B 被称为 A 的子类,A 则是 B 的父类(或超类)。
继承的主要优势在于子类可直接复用父类的属性与方法,无需重复编写代码。同时,子类可在继承基础上重新定义属性或重写方法,从而覆盖父类的原有功能,实现差异化行为。
JavaScript 实现面向对象编程的方式与传统基于类的语言存在显著差异。其核心机制依赖于原型和原型链,而非传统的类结构。尽管 ES6 引入了 class 关键字,但这仅是一种语法糖,底层依然基于原型机制运作。
接下来将重点介绍 ES5 中四种常用的继承实现方式。
ES5 实现的四种方式
1. 原型链继承
原型链继承的核心逻辑是让子类的原型对象指向父类的实例。当在子类实例中查找属性或方法失败时,引擎会沿着原型链向上查找父类原型,从而继承父类的属性和方法。
// 父类
function Person() {
this.name = 'gan'
}
Person.prototype.getName = function () {
return this.name
}
// 子类
function Student() { }
Student.prototype = new Person()
// 根据原型链的规则,顺便绑定constructor 不影响继承
Student.prototype.constructor = Student
const student = new Student()
console.log(student.name) // gan
console.log(student.getName()) // gan
缺点
1. 所有 Student 实例共享同一个 Person 实例。若 Person 包含引用类型属性,当某个 student1 实例修改该属性时,会影响所有其他实例。
function Person() {
this.hobby = ['篮球', '足球']
}
function Student() { }
Student.prototype = new Person()
const student1 = new Student()
const student2 = new Student()
student1.hobby.push('排球')
console.log(student1.hobby) // [ '篮球', '足球', '排球' ]
console.log(student2.hobby) // [ '篮球', '足球', '排球' ]
2. 子类实例无法向父类构造函数传递参数,即缺乏类似 super() 的功能。
2. 构造函数继承
构造函数继承通过在子类构造函数内部执行父类构造函数,并将父类构造函数的 this 绑定到子类实例上,使父类的属性和方法挂载到子类实例中。这种方式避免了多个子类实例共享同一个原型对象的问题,并支持向父类传递参数。
function Person() {
this.name = 'gan'
}
Person.prototype.getName = function () {
return this.name
}
function Student() {
Person.apply(this, arguments)
}
const student = new Student()
console.log(student.name) // gan
缺点
无法访问父类原型上的属性和方法。
console.log(student.getName) // undefined
3. 组合式继承
组合式继承结合了原型链继承和构造函数继承两种模式的优势。
function Person() {
this.name = 'gan'
}
Person.prototype.getName = function () {
return this.name
}
function Student() {
Person.apply(this, arguments)
}
Student.prototype = new Person()
Student.prototype.constructor = Student
const student = new Student()
console.log(student.getName()) // gan
缺点
每次创建子类实例时,父类构造函数会被调用两次(分别通过 Person.apply 和 new Person)。这导致原型中存在两份相同的属性和方法,虽然不影响继承逻辑,但显得不够优雅。
4. 寄生式组合继承
该方法解决了执行两次父类构造函数的问题,将子类的原型指向改为直接指向父类的原型。
function Person() {
this.name = 'gan'
}
Person.prototype.getName = function () {
return this.name
}
function Student() {
// 构造函数继承
Person.apply(this, arguments)
}
// Student.prototype = new Person()
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
const student = new Student()
console.log(student.getName()) // gan
这种方法被视为目前最优的解决方案。
总结
- 回顾继承机制,首先想到的是原型链继承:子类原型指向父类实例。但其缺陷在于子类实例处理父类引用类型时会出现共享问题,且无法向父类传递参数。
- 随后是构造函数继承:在子类构造函数中执行父类构造函数并绑定 this。其局限在于无法继承父类原型链上的属性和方法。
- 接着出现了组合式继承,它是前两者的结合体,但主要问题在于每次都会执行两次父类构造函数。
- 最终衍生出寄生式组合继承,解决了上述冗余问题。
至此,关于 JavaScript ES5 实现继承的四种常用方法小结完毕。如需了解更多 JavaScript ES5 继承相关内容,请搜索本站往期文章或继续阅读后续文章,期待大家的支持!
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
