JavaScript ES5与ES6七种继承方式详解
本文详细解析JavaScript中ES5实现的七种继承模式,包括原型链、借用构造函数、组合式、原型式、寄生式及寄生组合式继承,并对比ES6 Class语法。重点分析各模式的优缺点,如属性共享、参数传递及方法复用问题,帮助开发者根据场景选择最优继承方案。
在ES6发布之前,JavaScript前端开发中并不存在类语法的语法糖,因此无法像其他面向对象语言那样仅通过extends关键字即可轻松完成继承关系的构建,必须借助特定的技术手段来实现。本文将梳理并介绍几种常用的继承实现方式,内容主要基于《JavaScript高级程序设计》(红宝书)中关于继承章节的笔记与总结。
原型链继承
function Parent() {
this.name = 'arzh'
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child() {
}
//主要精髓所在
Child.prototype = new Parent()
Child.prototype.constructor = Child
var arzhChild = new Child()
arzhChild.getName() // 'arzh'
原型链继承存在以下主要缺点:
1. 所有实例共享引用类型的属性,导致对某一实例中引用类型属性的修改会反映到其他所有实例上。
function Parent() {
this.names = ['arzh','arzh1'];
}
function Child() {
}
//主要精髓所在
Child.prototype = new Parent()
Child.prototype.constructor = Child
var arzhChild2 = new Child()
arzhChild2.names.push('arzh2')
console.log(arzhChild2.names) //[ 'arzh', 'arzh1', 'arzh2' ]
var arzhChild3 = new Child()
arzhChild3.names.push('arzh3')
console.log(arzhChild3.names) //[ 'arzh', 'arzh1', 'arzh2', 'arzh3' ]
2. 在创建Child实例时,无法向Parent构造函数传递参数。这使得Child实例无法自定义初始化属性(如名称等)。
借用构造函数(经典继承)
function Parent() {
this.names = ['arzh','arzh1']
}
function Child() {
Parent.call(this)
}
var arzhChild2 = new Child()
arzhChild2.names.push('arzh2')
console.log(arzhChild2.names) //[ 'arzh', 'arzh1', 'arzh2' ]
var arzhChild3 = new Child()
arzhChild3.names.push('arzh3')
console.log(arzhChild3.names) //[ 'arzh', 'arzh1', 'arzh3' ]
该方式具有以下优点:
- 解决了引用类型属性被所有实例共享的问题,每个实例拥有独立的属性副本。
- 子类可以在调用父类构造函数时向父类传递参数。
function Parent(name) {
this.name = name
}
function Child(name) {
Parent.call(this, name)
}
var arzhChild = new Child('arzh');
console.log(arzhChild.name); // arzh
var arzhChild1 = new Child('arzh1');
console.log(arzhChild1.name); // arzh1
该方式存在以下缺点:
- 无法复用父类原型上的公共方法,导致内存浪费。
- 每次创建子类实例时,都必须重新执行一次父类构造函数,效率较低。
组合式继承(原型链继承与借用构造函数结合)
function Parent(name) {
this.name = name
this.body = ['foot','hand']
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
var arzhChild1 = new Child('arzh1', '18')
arzhChild1.body.push('head1')
console.log(arzhChild1.name,arzhChild1.age) //arzh1 18
console.log(arzhChild1.body) //[ 'foot', 'hand', 'head1' ]
var arzhChild2 = new Child('arzh2', '20')
arzhChild2.body.push('head2')
console.log(arzhChild2.name,arzhChild2.age) //arzh2 20
console.log(arzhChild2.body) //[ 'foot', 'hand', 'head2' ]
该方式结合了前两种模式的优点:
- 解决了引用类型属性被所有实例共享的问题。
- 子类可以向父类传递参数进行初始化。
- 实现了父类方法的复用,子类实例可以访问父类原型上的方法。
该方式存在以下缺点:
- 需要执行两次父类构造函数。第一次调用发生在
Child.prototype = new Parent(),第二次调用发生在Parent.call(this, name),这造成了不必要的性能浪费。
原型式继承
其核心原理是将传入的对象复制到新创建对象的原型上,从而实现继承关系。
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = {
name : 'arzh',
body : ['foot','hand']
}
var person1 = createObj(person)
var person2 = createObj(person)
console.log(person1) //arzh
person1.body.push('head')
console.log(person2) //[ 'foot', 'hand', 'head' ]
缺点:与原型链继承相同,所有实例共享引用类型的属性,对其中一个实例中引用类型属性的修改会影响其他实例。
寄生式继承
我们可以使用Object.create来替代上述createObj函数的实现,其基本原理相似。寄生式继承的核心在于在createObj函数内部以某种方式增强对象(例如添加新的方法),最后返回这个增强后的对象。
function createEnhanceObj(o) {
//代替原型式继承的createObj
var clone = Object.create(o)
clone.getName = function () {
console.log('arzh')
}
return clone;
}
通过createEnhanceObj函数,可以在创建对象的同时,将特定的方法通过此方式继承下来。
缺点:与借用构造函数类似,无法复用父类函数。每次创建对象时都会重新创建一遍方法,导致内存效率低下。
寄生组合式继承
这种模式避免了为了指定子类的原型而多次new父类构造函数。例如在Child.prototype = new Parent()中,只需复制父类原型的一个副本赋值给子类原型即可,无需调用父类构造函数。
function inheritPrototype(Parent, Child){
Child.prototype = Object.create(Parent.prototype) //创建父类原型的一个副本,把副本赋值给子类原型
Child.prototype.constructor = Child;
}
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(color) {
Parent.call(this, 'arzh')
this.color = color
}
inheritPrototype(Parent, Child)
var arzhChild = new Child('red')
console.log(arzhChild.name) // 'arzh'
优点:在指定子类型的原型时,无需调用父类型的构造函数,从而解决了组合式继承执行两次父类构造函数的缺点。
ES6继承
ES6引入了类的概念,支持通过class语法实现继承,代码结构更加简洁直观,具体代码如下:
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return this.x + '' + this.y
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y) //调用父类的constructor(x, y)
this.color = color
}
toString() {
return this.color + ' ' + super.toString() // 调用父类的toString()
}
}
var colorPoint = new ColorPoint('1', '2', 'red')
console.log(colorPoint.toString()) // red 12
总结
本文到此结束,希望上述内容能为您提供帮助。如果您觉得有用,欢迎持续关注本站以获取更多技术干货!
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
