JavaScript实现继承的6种常用方式总结
本文总结了JavaScript中实现继承的6种常用方式,包括原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承以及寄生组合式继承,并介绍了ES6 Class的实现机制。每种方式都分析了其优缺点及适用场景,帮助开发者根据实际需求选择合适的继承方案。
在JavaScript中,实现继承的核心目的是复用另一个对象的属性和方法。
1. 原型链继承
通过让一个构造函数的原型指向另一个类型的实例,使得该构造函数new出来的实例拥有该实例的属性。
当访问对象的属性时,JavaScript引擎会沿原型链向上搜索,依次检查该对象的原型、原型的原型,直到找到匹配的属性或到达原型链末尾。
function Parent() {
this.isShow = true
this.info = {
name: "mjy",
age: 18,
};
}
Parent.prototype.getInfo = function() {
console.log(this.info);
console.log(this.isShow);
}
function Child() {};
Child.prototype = new Parent();
let Child1 = new Child();
Child1.info.gender = "男";
Child1.getInfo(); // {name: 'mjy', age: 18, gender: '男'} ture
let child2 = new Child();
child2.isShow = false
console.log(child2.info.gender) // 男
child2.getInfo(); // {name: 'mjy', age: 18, gender: '男'} false
优点:实现方式简洁直观,易于理解。
缺点:所有实例共享继承的属性和方法;创建子类型实例时无法传递参数,因为对象是一次性创建的,缺乏定制化能力。
2. 借用构造函数继承
function Parent(gender) {
this.info = {
name: "yhd",
age: 19,
gender: gender
}
}
function Child(gender) {
Parent.call(this, gender)
}
let child1 = new Child('男');
child1.info.nickname = 'xiaoma'
console.log(child1.info);
let child2 = new Child('女');
console.log(child2.info);
在子类型构造函数内部调用父类型构造函数,利用apply()或call()方法将父对象的构造函数绑定到子对象上。
优点:解决了原型链继承中无法传参和父类原型共享的问题。
缺点:方法必须在构造函数内定义,导致无法实现函数复用;父类型原型中定义的方法对子类型不可见,所有类型只能使用构造函数模式。
3. 组合继承(经典继承)
结合原型链和借用构造函数:使用原型链继承原型属性和方法,通过借用构造函数继承实例属性。这样既实现了函数复用,又保证每个实例拥有自己的属性。
function Person(gender) {
console.log('执行次数');
this.info = {
name: "mjy",
age: 19,
gender: gender
}
}
Person.prototype.getInfo = function () { // 使用原型链继承原型上的属性和方法
console.log(this.info.name, this.info.age)
}
function Child(gender) {
Person.call(this, gender) // 使用构造函数法传递参数
}
Child.prototype = new Person()
let child1 = new Child('男');
child1.info.nickname = 'xiaoma'
child1.getInfo()
console.log(child1.info);
let child2 = new Child('女');
console.log(child2.info);
优点:解决了原型链继承和借用构造函数继承各自的局限性。
缺点:无论何种情况,都会调用两次超类型构造函数:一次在创建子类型原型时,另一次在子类型构造函数内部。
4. 原型式继承
方法一:借用构造函数
在函数A内部创建一个临时构造函数,将传入对象作为其原型,最后返回该临时类型的新实例。本质上是对传入对象执行了一次浅复制。
function createObject(obj) {
function Fun() {}
Fun.prototype = obj
return new Fun()
}
let person = {
name: 'mjy',
age: 18,
hoby: ['唱', '跳'],
showName() {
console.log('my name is:', this.name)
}
}
let child1 = createObject(person)
child1.name = 'xxxy'
child1.hoby.push('rap')
let child2 = createObject(person)
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']
方法二:Object.create()
Object.create()将现有对象的属性挂到新建对象的原型上,新建对象本身为空对象。
ECMAScript 5通过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']
优点:无需单独创建构造函数。
缺点:属性中包含的引用值始终在相关对象间共享,子类实例无法向父类传参。
5. 寄生式继承
思路与原型式继承和工厂模式类似:创建一个封装继承过程的函数,内部以某种方式增强对象,最后返回对象,仿佛它完成了所有工作。
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']
优点:写法简单,无需单独创建构造函数。
缺点:通过寄生式继承添加函数会导致难以重用,因无法实现函数复用而降低效率,这一点与构造函数模式类似。
6. 寄生组合式继承
前文提及,组合继承为经典且常用的继承模式,但其核心缺陷在于父类构造函数会被调用两次:一次发生于子类型实例化过程中,另一次发生于子类型构造函数内部执行时。寄生组合继承旨在优化此过程,以降低调用父类构造函数的开销。
通过借用构造函数继承属性,通过原型链混成形式继承方法。本质上是用寄生式继承继承超类型原型,再将结果指定给子类型原型。
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); // ['唱', '跳']
优点:高效率,只调用一次父构造函数,避免在子原型上创建不必要、多余的属性;同时保持原型链不变。
缺点:代码复杂度较高。
7. ES6、Class实现继承
ES5的继承实质是先创造子类的实例对象this,再将父类方法添加到this上(Parent.apply(this))。ES6的继承机制不同,实质是先将父类实例对象的属性和方法加到this上(因此必须先调用super方法),再用子类构造函数修改this。
优点:语法简单易懂,操作更便捷。
缺点:并非所有浏览器都支持class关键字。
以上就是JavaScript实现继承的6种常用方式总结的详细内容,更多关于JavaScript继承的资料请关注本站其它相关文章!
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
