面向对象中的继承

在js中常常提到面向对象,面试时候也会经常问到继承,也常常听到一句话:面向对象编程。
本篇文章主要讲解的是继承。认真话10分钟看完,自己画一画关系图,相信可以轻松掌握,再也不怕面试中被问到。

面向对象的特点

  • 封装(Encapsulation): 把相关的信息(无论数据或方法)存储在对象中的能力
  • 继承(Inheritance): 由另一个类(或多个类)得来类的属性和方法的能力
  • 多态(Polymorphism): 编写能以多种方法运行的函数或方法的能力

继承

  • 如果你对原型、原型链理解不是特别深刻的话,可以先看一下原型、原型链相关文章,只有理解了原型和原型链相关知识,你才可以很好的理解继承。
    原型和原型链可以看我之前的文章:《彻底理解原型和原型链》

继承的几种实现方式

  • 这里主要用es5去实现继承,因为es6有继承相关的语法糖,这里就不展示了。

借助构造函数实现继承

  • 先上代码:
    function Parent1(){
        this.name='Parent1'
    };
    function Child1(){
        Parent1.call(this);//apply
        this.type='Child1'
    }
    console.log(new Child1)
  • 打印结果
wqyAQx.png
  • 上面我们借助构造函数通过call apply改变指向实现继承,但这种继承方式有一个缺点:继承不了父类原型对象上的属性,只能继承构造函数内的属性
    验证一下我们的结论:
    function Parent1(){
        this.name='Parent1'
    };
    Parent1.prototype.say=function(){
        console.log(111)
    }
    function Child1(){
        Parent1.call(this);//apply
        this.type='Child1'
    }
    console.log(new Child1)
  • 打印结果
wqyAQx.png
  • 可以看出,原型对象上say方法并没有被继承过来,只能继承构造函数内的一些属性。

借助原型链实现继承

  • 还是先上代码:
    function parent2(){
        this.name='parent2',
        this.play=[1,2,3,4]
    }
    function Child2(){
        this.type='Child2'
    }
    Child2.prototype=new parent2()
    console.log(new Child2)
    //验证关系
    console.log(new Child2().__proto__==Child2.prototype)
  • 输出结果:
wqyNTg.png
  • 但它还是有缺点,实例出来的是共用的,直接上代码演示一下:
    //接上面代码~
    var s1=new Child2()
    var s2=new Child2()
    s1.play.push(5)
    console.log(s1.play,s2.play)
    //他们俩是公用的
    console.log(s1.__proto__===s2.__proto__)
  • 打印结果:
wqyRk4.png

组合继承(借鉴上面两个的优点)

  • 上面两个方法虽说可以实现,但是还是有很大缺点的,下面看一下组合继承实现,先上代码:
    function Parent3(){
        this.name='Parent3',
        this.play=[1,2,3]
    }
    function Child3(){
        Parent3.call(this)
        this.type='Child3'
    }
    Child3.prototype=new Parent3()
    let s3=new Child3()
    let s4=new Child3()
    s3.play.push(4)
    console.log(s3.play)
    console.log(s4.play)
  • 打印一下结果:
wq6kNQ.png
  • 可以看到,这种实例出来的原型是不会共用的,但它还是有个缺点,在继承时候执行了多次父类( Parent3.call(this)和Child3.prototype=new Parent3()),显然还不够优秀,我们可以改造一下:
    function Parent4(){
        this.name='Parent4',
        this.play=[1,2,3]
    }

    function Child4(){
        Parent3.call(this)
        this.type='Child3'
    }

    Child4.prototype=Parent4.prototype
    let s5=new Child4()
    let s6=new Child4()
    s5.play.push(5)

    console.log(s5.play)
    console.log(s6.play)
    console.log(s5 instanceof Child4,s5 instanceof Parent4)
    console.log(s5.constructor)
  • 输出结果:
wq6hVS.png
  • 我们虽然解决了父类构造函数执行两次的问题,但我们又迎来了新的问题:s5.constructor输出结果为Parent4,这显然是不行的,因为constructor指向应该是Child4instanceof也判断不了是来自于谁。那我们还得进行一番改造:
    function Parent5(){
        this.name='parent2',
        this.play=[1,2,3,4]
    }
    function Child5(){
        Parent3.call(this)
        this.type='Child5'
    }
    Child5.prototype=Object.create(Parent5.prototype) //__proto__
    Child5.prototype.constructor=Child5
    console.log(new Child5)

    var s7=new Child5();
    console.log(s7 instanceof Child5,s7 instanceof Parent5)
    console.log(s7.constructor)
  • 执行结果:
wq6qK0.png
  • 上面通过手动给Child5constructor赋值,这样就可以通过constructor来看是由哪个函数实例出来的。

结尾

理解继承,还是需要去理解原型和原型链的知识,这块知识在JavaScript中是非常难理解的,学习过程中,可以画一下他们的关系图,以及看一下console.log信息中的**proto**来辅助理解。如果对你有帮助,记得 关注 + 点赞+收藏