聊一聊js的各种继承方式

本文深入探讨了JavaScript中的继承实现,包括原型链继承、构造函数继承、组合继承和寄生组合继承。针对每种方式,文章详细阐述了其工作原理、存在的问题以及如何解决这些问题。例如,原型链继承可能导致引用类型的共享问题,而构造函数继承则会因重复创建属性而浪费内存。寄生组合继承则作为优化方案,避免了上述问题。此外,还提及了ES6中的Class继承作为简洁的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.原型链继承


我们知道js对象的属性查找流程,是先查找本对象的属性,查找不到就会从 原型对象 上查找(每个实例对象都有会__proto__属性指向 原型对象 ),还是找不到那就__proto__.__proto__ ,原型对象上的原型对象,这样一路往上找,找到为止或者__proto__值为null。
我们可以使用这种查找原理来实现js对象继承

function Parent(){
    this.name = 'my name is Parent'
    this.actions = ['sing','jump']
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(){
   
}
//原型链继承
Child.prototype = new Parent()
//保证类型正确,指向Child函数
Child.prototype.constructor = Child

const child1 = new Child()
child1.getName();    //my name is Parent

 

这种继承方式很简单,接下来再看一段代码

//先来构建两个实例
const child2 = new Child()
const child3 = new Child()


child2.actions.pop();
console.log(child1.action) //['sing']
console.log(child2.action) //['sing']

显然,这不是我们想要的结果

问题:

1. 当父亲出现引用类型的时候,某个子实例继承了父类的引用类型属性,一旦某个子实例修改了这个引用类型里面的值,其他子实例继承的这个属性全部都会被修改

2. 不能传参到父类

 

2.构造函数继承

通过 原型链继承的方式 引申出来的问题,我们只要想办法把 Parent 上的 属性方法,添加到 Child 上,而不是放在原型对象上,就可以防止被其他实例共享。

function Parent(){
    this.name = 'my name is Parent'
    this.actions = ['sing','jump']
    this.eat = function(){
        console.log(`${this.name} - eat`)
    }
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(){
       //构造函数继承,可以传参
       Parent.apply(this,arguments)
}


const child1 = new Child()
const child2 = new Child()

child1.actions.pop()

console.log(child1.actions) //['sing'] 
console.log(child2.actions) //['sing','jump'] 

这样看起来是不是就很完美的实现继承了, 但是 再仔细想想 会有什么问题呢?

console.log(child1.eat === child2.eat)  //false

问题:属性或者方法想要被继承,就必须放在构造函数中定义,但是这样每次创建实例的时候,都会创建一遍父类的属性和方法, 多占用一块内存

 

3.组合继承

a. 通过 原型链继承 的方式, 我们 可以 实现基本的继承, 但是 父类的 引用类型属性 会被所有实例共享,一旦某个实例修改后,其他实例全部都会修改,而且这种继承方式不能传参到父类

b. 通过 构造函数继承 的方式,我们 解决了 原型链继承 的 两个问题(父类引用和传参问题), 但是 由于 每次创建实例,构造函数都会重复创建一遍属性和方法,会导致内存占用过多 的问题

那么 我们 结合一下 这两种 继承 方式

 

function Parent(){
    this.name = 'my name is Parent'
    this.actions = ['sing','jump']
  
}
Parent.prototype.eat = function(){
    console.log(`${this.name} - eat`)
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(){
       //构造函数继承,可以传参
       Parent.apply(this,arguments)
}

//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()

child1.eat(); // c1 - eat
child2.eat(); // c2 - eat

console.log(child1.eat === child2.eat); // true

那么,这样其实就已经实现继承,并且解决上面的问题了。

但是,再仔细观察,我们会发现, 上面调用了2次 构造函数,Parent.apply() 和 new Parent() , 明显也是一种资源浪费,是否可用再次精简呢

 

4.寄生组合继承

仔细观察,我们可以发现 其实 new Parent() 里面创建的属性和方法其实已经是不需要的,已经在Parent.apply()子构造函数里面创建了, 那么我们就可以精简这一步

function Parent(){
    this.name = 'my name is Parent'
    this.actions = ['sing','jump']
  
}
Parent.prototype.eat = function(){
    console.log(`${this.name} - eat`)
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(){
       //构造函数继承,可以传参
       Parent.apply(this,arguments)
}

//原型链继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()

child1.eat(); // c1 - eat
child2.eat(); // c2 - eat

console.log(child1.eat === child2.eat); // true

我们只需要简单的修改一下 代码,就可以解决两次调用构造函数的问题, Object.create 的效果其实跟下面的代码一样,通过一个空对象做原型中转,来跳过 Parent上面的属性和方法

let TempFunction = function () {};
TempFunction.prototype = Parent.prototype;
Child.prototype = new TempFunction();

这里再 引申 思考一个问题。有思考到答案的小伙伴可以在评论区写上~~

问:  为什么不直接  Child.prototype = Parent.prototype   ?

 

5.Class 继承

毋庸置疑,这种方式最简单,最直观

class Parent {
    constructor() {
        this.name = 'aaa';
    }

    getName() {
        console.log('getname');
    }
}

class Child extends Parent {
    constructor() {
        super();
    }
}

const p1 = new Child();
p1.getName(); //aaa

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一杯码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值