继承的几种方式

说起继承,又是一个老生常谈的问题了。今天来讲讲继承的几种方法以及他们的优缺点吧。

源码地址:点击这里

一、原型链继承

原型链继承:通过原型将一个引用类型继承另一个引用类型的属性和方法。

// 构建一个父类
function Parants (){this.name = 'zs',this.age = 18this.hubby = {study: 'js',read: '西游记'}
}
// 定义方法
Parants.prototype.getHubby = function () {console.log(this.name)console.log(this.hubby)
}// 构造子类
function Child() {}
// 子类的原型继承父类
Child.prototype = new Parent()// 以子类原型创建一个新的实例
let Child1 = new Child()
Child1.hubby.song = 'like'
Child1.getHubby ()  // {study: 'js', read: '西游记', song: 'like'}let Child2 = new Child()
Child2.getHubby ()  // {study: 'js', read: '西游记', song: 'like'}// 可以看到这里 Child2,并没有去修改 Params的属性
// 但是打印数据不是原始数据,而是 Child1 修改之后的数据

总结优点:

  1. 父类的方法可以复用

缺点:

  1. 父类所有的引用属性(hubby),会被所有的子类共享,其中一个子类修改引用属性,其他的子类也会受到影响
  2. 子类型实例不能给父类型构造函数进行传参

二、借用构造函数继承

借用构造函数继承:在子类构造函数内部调用超类型构造函数。 通过使用 apply() 和 call() 方法可以在新创建的子类对象上执行构造函数。

// 构建父类
function Parants (){this.hubby = {study: 'js',read: '西游记'}
}
// 构建子类,使用 call 把父类的 this 指向子类
function Child() { Parants.call(this)
}
// 利用子类创建一个新的实例
let child1 = new Child()
child1.hubby.song = 'like'
console.log(child1.hubby) // {study: 'js', read: '西游记', song: 'like'}let child2 = new Child()
console.log(child2.hubby) // {study: 'js', read: '西游记'}
// 这里可以看到,就算child1 修改了属性,child2 也不会受到影响

这里使用 call() 或者是 apply()的方法重新执行上下文,就相当于是这个新的 Child 实例对象上面重新执行了Parant 构造函数中的所有代码,就相当于是每一个利用 Child 构造函数来创建的对象都有自己的 hubby 方法,实例之间互不影响。

关于传参

相比于原型链继承,借用构造函数有一个优点,那就是可以在子类构造函数中向父类构造函数进行传参。

// 构造父类
function Parant (name) {// name:用来接收传过来的参数this.info = {// 将传来的参数赋值给一个属性name: name}
}
// 构造子类
function Child(name) {// 继承父类,并且向父类进行传参
Parant.call(this, name)
}
// 创建新的实例,并传参
let child1 = new Child('zs')
// 打印 info
console.log(child1.info) // {name: 'zs'}let child2 = new Child('ls')
// 打印 info
console.log(child2.info) // {name: 'ls'}

通过上面的例子,我们可以看到,在 Params 上面接收一个参数,并赋值给一个属性,然后在子类继承父类的构造函数时,由子类进行传参,这样就可以实现不同的实例对象使用同一个构造函数,但内部属性不同。

总结优点:

  1. 可以在子类的构造函数中向父类进行传参
  2. 父类的引用属性不会在子类中共享

缺点:

  1. 子类不能访问父类原型上定义的方法(即不能访问Parent.prototype上定义的方法),因此所有方法属性都写在构造函数中,每次创建实例都会初始化

三、组合继承(原型链+借用构造函数)

组合继承:将原型链借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。

// 构造父类
function Parant(age) {this.age = agethis.name = ['jack', 'rose']
}// 构造父类方法
Parant.prototype.sayAge = function() {console.log(this.age)
}// 构造子类
function Child(age, danger) {// 将父类的 this 指向子类Parant.call(this, age)this.danger = danger
}
// 子类继承父类的原型
Child.prototype = new Parant()// 构造子类的方法
Child.prototype.sayDanger = function (){console.log(this.danger)
}let child1 = new Child('19', '男')
child1.name.push('tom')
console.log(child1.name) //  ['jack', 'rose', 'tom']
child1.sayDanger() // 男
child1.sayAge() // 19let child2 = new Child('22', '女')
child2.name.push('lucy')
console.log(child2.name) //  ['jack', 'rose', 'lucy']
child2.sayDanger() // 女
child2.sayAge() // 22

通过上面的例子,我们可以看到,Parant 构造函数上设置了 agename 两个属性,然后又在原型上添加了一个 sayAge 的方法。Child构造函数通过使用 call继承了 Parant构造函数的属性,传递了一个 danger 参数,同时给自身的原型上添加 sayDanger 的方法,这样创建的两个实例,既有自己的属性,包括继承的父类的 name,同时也拥有父类的 sayDanger 方法。

总结优点:

  1. 父类的方法可以复用
  2. 可以在Child构造函数中向Parent构造函数中传参
  3. 父类构造函数中的引用属性不会被共享

缺点:

  1. 需要执行两次父类构造函数:

    • 第一次是Child.prototype = new Parent()
    • 第二次是Parant.call(this, age)造成不必要的浪费

四、型式继承

型式继承:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。

// 创建一个构造函数,将接收到的参数赋值到原型上
function NewFn(obj) {return  NewFn.prototype = obj
}// 创建一个对象
let person = {name: 'zs',age: 18,friends: ['jack', 'tom', 'rose'],sayName:function() {console.log(this.name);}
}
// 构造实例1
let person1 = NewFn(person)
person1.name = 'ls'
person1.friends.push('lucy')
person1.sayName() // ls
console.log(person1.friends) // ['jack', 'tom', 'rose', 'lucy']// 构造实例2
let person2 = NewFn(person)
person2.name = 'ww'
person2.sayName() // ww
console.log(person2.friends) // ['jack', 'tom', 'rose', 'lucy']

通过上面的例子,我们可以看到,这种继承方式主要是通过一个对象,fn()对传入其中的对象执行了一次浅复制,将构造函数 NewFn 的原型直接指向传入的对象。

基本使用起来和第一种的原型链继承差不多。

总结优点:

  1. 父类的方法可以复用

缺点

  1. 父类所有的引用属性(friends),会被所有的子类共享,其中一个子类修改引用属性,其他的子类也会受到影响
  2. 子类型实例不能给父类型构造函数进行传参

五、寄生式继承

寄生式继承:寄生式继承是原型式继承的加强版。创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。

// 创建一个构造函数,将接收到的参数赋值到原型上
function NewFn(obj) {return  NewFn.prototype = obj
}// 再创建一个函数,调用上面的构造函数
function createObj (original) {let clone = fn(original)// 用一个变量接收,添加一个方法clone.sayName = function() {console.log(this.name)}
// 最后把这个结果返回
return clone
}let person = {name: 'zs',age: 18,friends: ['jack', 'tom', 'rose']
}// 构造实例
let person1 = createObj(person)
person1.friends.push("lucy")
console.log(person1.friends) //  ['jack', 'tom', 'rose', 'lucy']
person1.sayName() // zslet person2 = createObj(person)
console.log(person2.friends) //  ['jack', 'tom', 'rose', 'lucy']

通过上面的例子,我们可以看出,基本实现的效果和型式继承差不多,person1 修改了 ,person2 调用父类的引用数据也会随之发生改变。

总结缺点:

缺点(同原型式继承):

  1. 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  2. 无法传递参数

六、组合式寄生继承

组合式寄生继承:通过借用函数来继承属性,通过寄生式继承来继承方法。

function NewFn(obj) {return  NewFn.prototype = obj
}
// 构造父类
function Parant(age) {this.age = agethis.name = ['jack', 'rose']
}
// 构造父类方法
Parant.prototype.sayAge = function() {console.log(this.age)
}// 构造子类
function Child(age, danger) {// 将父类的 this 指向子类Parant.call(this, age)this.danger = danger
}
// 子类继承父类的原型
Child.prototype = new Parant()function inheritPrototype(child, parent) {let prototype = NewFn(parent.prototype); // 创建对象prototype.constructor = child; // 增强对象Child.prototype = prototype; // 赋值对象
}inheritPrototype(Child, Parant);
// 构造子类的方法
Child.prototype.sayDanger = function (){console.log(this.danger)
}let child1 = new Child("男", 23);
child1.sayAge(); // 23
child1.sayDanger(); // 男
child1.name.push("jack");
console.log(child1.name); // ['jack', 'rose', 'jack']let child2 = new Child("女", 18);
child2.sayAge(); // 18
child2.sayDanger(); // 女
console.log(child2.name); // ['jack', 'rose']

总结优点:

  1. 只调用一次父类构造函数
  2. Child可以向Parent传参
  3. 父类方法可以复用
  4. 父类的引用属性不会被共享

寄生式组合继承可以算是引用类型继承的最佳模式

七、ES6 的 class 继承

1、使用 class 构造一个父类

class Parent {constructor(name,age){this.name = namethis.age = age}sayName(){console.log(this.name);}
}

2、使用 class 构造一个子类,并使用 extends 实现继承,super 指向父类的原型对象

class Child extends Parent{constructor(name,age,gender){super(name,age)this.gender = gender}sayGender(){console.log(this.gender);}
}

3、实例化对象

const person1 = new Child('zs',18,'男')
person1.sayGender() // 男
person1.sayName() // zs
console.log(person1.name); // zs
console.log(person1.age); // 18const person2 = new Child('ls',28,'女')
person2.sayGender() // 女
person2.sayName() // ls
console.log(person2.name); // ls
console.log(person2.age);// 28

可以实现继承的几种方式相关推荐

  1. JS 总结之原型继承的几种方式

    在之前的总结中,我们详细分析了原型<JS 总结之原型>,原型很大作用用于模拟继承,这一次,我们来聊原型继承的几种方式. function Person (age) {this.age = ...

  2. Django中Model继承的三种方式

    Django中Model继承的三种方式 Django中Model的继承有三种: 1.抽象继承 2.多表继承 3.proxy model(代理model) 1.抽象继承 第一种抽象继承,创建一个通用父类 ...

  3. 继承有几种方式,分别是什么,想要实现继承可以使用哪些方法

    这里是修真院前端小课堂,每篇分享文从 [背景介绍][知识剖析][常见问题][解决方案][编码实战][扩展思考][更多讨论][参考文献] 八个方面深度解析前端知识/技能,本篇分享的是: [继承有几种方式 ...

  4. (转)js实现继承的5种方式

    js是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承有以下通用的几种方式 1.使用对象冒充实现继承(该种实 ...

  5. JavaScript笔记 - 对象继承的几种方式

    1)对象冒充 2)call方式 3)apply方式 4)原型链 5)混合方式 1)对象冒充 Js代码 function People(name, age) { this.name = name; th ...

  6. JS 总结之原型继承的几种方式 1

    前提 以一个父类为前提条件,列举 js 继承的继承方式: function Person (age) {this.age = age || 18 } Person.prototype.sleep = ...

  7. tkinter 类继承的三种方式

    tkinter class继承有三种方式. 提醒注意这几种继承的运行方式 一.继承 object 1.铺tk.Frame给parent: 说明: self.rootframe = tk.Frame(p ...

  8. JavaScript 实现继承的5种方式

    js是一个面向对象的语言,所以具备一些面向对象的方式----------例如继承.接下来介绍5种js的继承方式.注意:js 中的函数其实是对象,函数名是对 Function 对象的引用. 1.采用ca ...

  9. JavaScript实现继承的几种方式

    1.原型链 基本思想:利用原型让一个引用类型继承另外一个引用类型的属性和方法. 构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原 ...

最新文章

  1. 双11还是那个双11,双12却早已不是那个双12
  2. CentOS7安装iptables防火墙的方法
  3. windows平台下caffe可视化配置
  4. 小白学数据分析-----数据指标 累计用户数的使用
  5. SpringBoot 自带工具类~AopUtils
  6. maven私服搭建:docker安装Sonatype Nexus以及寻找admin用户对应的随机初始密码
  7. Modifying a Dynamic Library Without Changing the Source Code
  8. 官方授权正版 Avast AntiTrack Premium 反跟踪工具软件
  9. psftp查看服务器上的文件,PSFTP自动登录SFTP服务器,上传指定文件...
  10. 虚拟内存怎么设置最好?虚拟内存设置多少合适
  11. 方舟破解版自建服务器,方舟生存进化自建服务器教程
  12. Django的Forms.py
  13. 波卡的盔甲与弱肋 |链捕手
  14. 您能不能也宽容一点??
  15. 树莓派 电脑通过界面远程控制
  16. 小开销实现工作日的一个方法
  17. 2022跨年烟花代码(六)HTML5鼠标点击页面放烟花特效
  18. 会做饭,擅长烹饪,会给你的工作生活带来怎样的不同?
  19. 西门子低代码平台通过Database Connector 连接Mysql 实现增删改查
  20. SOC安全运营中心(一) OSSIM安装

热门文章

  1. mysql 电子书_MySQL开发与实践 (付森等著) 中文pdf扫描版[45MB]
  2. 第十五次 Java作业
  3. c# 变量不可访问,因为它具有一定的保护级别
  4. FPGA/数字IC实用笔试面试刷题汇总
  5. 写一些“北京中新委互联网传媒”的黑历史吧!
  6. 电容老化失效测试解决方案
  7. 《C++笔记 环境搭建》第0章 Windows下C++环境搭建
  8. pytorchOCR之目录层级结构说明
  9. 马思特php,全面解析曝光图马思特t300和t300rs有何区别?哪个好?爆料真实使用心得...
  10. jQuery练习t300,从0到1