JS 组合继承

简介: JS 组合继承

继承也是面向对象的特性之一,但是在 ES6 版本之前是没有 extends 去实现继承的,我们只能通过 构造函数 和 原型对象 来实现继承,其中分别为构造函数来继承属性,原型对象来继承方法,这种继承模式被称为 组合继承




文章目录:

一:call() 的作用与使用


1.1 使用 call() 来调用函数


1.2 使用 call() 来改变 this 的指向


二:利用构造函数继承父属性


2.1 实现过程


2.1 实现过程分析


三:利用原型对象继承父方法


3.1 继承父方法的错误演示


问题原因


3.2 继承父方法的正确做法


 3.2 继承父方法的注意事项


一:call() 的作用与使用

     在开始讲解组合继承前我们先来了解一下 call() 方法,call() 方法可以改变 this 的指向,也可以调用函数等等,最主要的还是其改变指向的作用


语法格式 call( 目标this指向,参数1,参数2 ......)

1.1 使用 call() 来调用函数

call() 可以拿来直接用来调用函数


    <script>

       function eat(){

           console.log('我在吃午饭');

       }

       eat.call()

    </script>



1.2 使用 call() 来改变 this 的指向

call() 的第一个参数为你要改变的 this 的指向,这里的 this 指的是 call 的调用者,此处函数调用不指定的话即指向 window,指定让其指向新创建的对象 obj,只需要让其第一个参数为 obj 对象即可,所以结果应该是第一个为 window,第二个为 obj 对象


    <script>

       function eat(){

           console.log(this);

       }

       var obj={

           'name':'小明',

           'age':18

       }  

       eat.call()

       eat.call(obj)

    </script>


二:利用构造函数继承父属性

我们已经知道组合继承是由构造函数和原型对象一起来实现的,其中构造函数实现的是属性的继承,原型对象实现的是方法的继承,这版块就走进利用父构造函数完成属性的继承




2.1 实现过程

    其实现非常容易,只需要在子构造函数中,使用 call 调用父构造函数(将其当做普通函数调用),其中在 call 方法中更改父构造函数中的 this 指向,由于 call 方法是在子构造函数中调用的,所以此处当做参数的 this 代表父构造函数中的 this 指向子构造函数的实例化对象,并传参进去,所以相当于给子构造函数的实例化对象添加了属性并赋值


    <script>

       //声明父构造函数

       function Father(uname,uage,utel,sex){

           this.uname=uname;

           this.uage=uage;

           this.utel=utel;

           this.sex=sex;

       }

       //声明子构造函数,但是想继承父类的uname,uage,utel等等属性的赋值操作

       function Son(uname,uage,utel,sex){

           Father.call(this,uname,uage,utel,sex)

       }

       var son1=new Son('张三',19,12345,'男')

       console.log(son1);

    </script>




2.1 实现过程分析

首先在子构造函数中使用 call 调用了父构造函数,并传参给 call 的参数,其中第一个参数为 this 指向的改变,其余为带入的属性值参数

我们知道构造函数中的 this 指向其实例化对象,所以本身父构造函数的 this 应该指向父构造函数的实例化对象,而此处 call 方法调用在子构造函数中,所以参数的指向更改为指向子构造函数的实例化对象

此处子构造函数的实例化对象就是 son1,所以父构造函数中的 this 指向的均是 son1,

所以就给 son1 添加并赋值了 uname,uage 等等属性



三:利用原型对象继承父方法

组合继承的最后一版块,利用原型对象来继承方法,此处我们说明的是存放在构造函数的原型对象里的公共方法的继承


3.1 继承父方法的错误演示

错误的继承就是直接将父亲的原型对象赋值给子的原型对象,这样确实也可行,但是如果给子原型对象添加子类特有的方法,那父原型对象也会加上这个方法


    <script>

       //声明父构造函数

       function Father(uname,uage){

           this.uname=uname;

           this.uage=uage;

       }

       Father.prototype.money=function(){

           console.log('我有很多钱');

       }

       //声明子构造函数

       Son.prototype=Father.prototype;

       function Son(uname,uage){

           Father.call(this,uname,uage)

       }

       var father1=new Father('爸爸',40)

       var son1=new Son('儿子',19)

       console.log(father1);

       console.log(son1);

    </script>


我们可以发现父子的原型对象中确实都有了这个方法,证明确实这个办法是行得通的




但是其也有问题存在,当我们想给子原型对象单独添加其特有的方法时,就会出问题


上述问题给子原型对象添加特有方法的错误示例:

    <script>

       //声明父构造函数

       function Father(uname,uage){

           this.uname=uname;

           this.uage=uage;

       }

       Father.prototype.money=function(){

           console.log('我有很多钱');

       }

       //声明子构造函数

       Son.prototype=Father.prototype;

       Son.prototype.school=function(){

           console.log('我去上学了');

       }

       function Son(uname,uage){

           Father.call(this,uname,uage)

       }

       var father1=new Father('爸爸',40)

       var son1=new Son('儿子',19)

       console.log(father1);

       console.log(son1);

    </script>


我们发现,我们确实给儿子添加上了儿子特有的方法,但是,父亲的原型对象内也加上了这个方法,这并不满足我们的预期,原因分析如下




问题原因

问题就在于我们的原型对象也是对象,对象是引用数据类型,引用数据类型的对象本质是在堆内存存放,是不能直接访问的,其访问是通过栈内存上的引用地址来找到去访问,而我们此处采用的等号赋值的方式,实际上是将其在栈内存上的引用地址拷贝过去了,二者指向了同一块内存空间,所以更改子原型对象,父原型对象也改变了




3.2 继承父方法的正确做法

正确的做法是让其子原型对象对象等于父实例化对象  Son.prototype=new Father(),其实我感觉有种高内聚低耦合的韵味,减少了直接联系从而解决问题




    <script>

       //声明父构造函数

       function Father(uname,uage){

           this.uname=uname;

           this.uage=uage;

       }

       Father.prototype.money=function(){

           console.log('我有很多钱');

       }

       //声明子构造函数

       Son.prototype=new Father();

       Son.prototype.school=function(){

           console.log('我去上学了');

       }

       function Son(uname,uage){

           Father.call(this,uname,uage)

       }

       var father1=new Father('爸爸',40)

       var son1=new Son('儿子',19)

       console.log(father1);

       console.log(son1);

    </script>


问题得以解决,子原型对象有了自己特有的方法,并且也继承了父亲原型对象中的方法




 3.2 继承父方法的注意事项

我们以 Son.prototype=new Father() 这种方法继承,看似已经天衣无缝,其实我们早就说过,采用等号赋值的方法会造成原型对象被覆盖,里面的构造函数 constructor 会被覆盖掉,需要我们手动返回,所以七千万要记得手动返回 constructor


    <script>

       //声明父构造函数

       function Father(uname,uage){

           this.uname=uname;

           this.uage=uage;

       }

       Father.prototype.money=function(){

           console.log('我有很多钱');

       }

       //声明子构造函数

       Son.prototype=new Father();

       Son.prototype.constructor=Son;  //手动返回构造函数constructor

       Son.prototype.school=function(){

           console.log('我去上学了');

       }

       function Son(uname,uage){

           Father.call(this,uname,uage)

       }

       var father1=new Father('爸爸',40)

       var son1=new Son('儿子',19)

       console.log(father1);

       console.log(son1);

       console.log(Son.prototype.constructor);

    </script>


相关文章
|
4月前
|
设计模式 JavaScript 前端开发
在JavaScript中,继承是一个重要的概念,它允许我们基于现有的类(或构造函数)创建新的类
【6月更文挑战第15天】JavaScript继承促进代码复用与扩展,创建类层次结构,但过深的继承链导致复杂性增加,紧密耦合增加维护成本,单继承限制灵活性,方法覆盖可能隐藏父类功能,且可能影响性能。设计时需谨慎权衡并考虑使用组合等替代方案。
45 7
|
4月前
|
JavaScript 前端开发
在 JavaScript 中,实现继承的方法有多种
【6月更文挑战第15天】JavaScript 继承常见方法包括:1) 原型链继承,利用原型查找,实例共享原型属性;2) 借用构造函数,避免共享,但方法不在原型上复用;3) 组合继承,结合两者优点,常用但有额外开销;4) ES6 的 class,语法糖,仍基于原型链,提供直观的面向对象编程。
34 7
|
29天前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
1月前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
1月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
1月前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
17 1
|
1月前
|
JavaScript 前端开发
JS的几种继承方式
JavaScript中的几种继承方式视频。
12 0
|
2月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
80 0
|
2月前
|
JavaScript 前端开发 开发者
揭开JavaScript的神秘面纱:原型链背后隐藏的继承秘密
【8月更文挑战第23天】原型链是JavaScript面向对象编程的核心特性,它使对象能继承另一个对象的属性和方法。每个对象内部都有一个[[Prototype]]属性指向其原型对象,形成链式结构。访问对象属性时,若当前对象不存在该属性,则沿原型链向上查找。
29 0
|
2月前
|
JavaScript 前端开发
JS的6种继承方式
JS的6种继承方式