JS中的几种实现继承的方式

文章详细介绍了JavaScript中的多种继承方式,包括原型继承、借用继承、组合继承、拷贝继承及其变种,以及ES6中的类继承。每种继承方式的优缺点被详细阐述,如组合继承能同时继承属性和方法,但存在性能问题,而ES6的类继承则是通过`extends`关键字实现,底层基于寄生式组合继承。
摘要由CSDN通过智能技术生成

概念

为什么继承:

​ 1. 为了把公共的内容提取出来变成更加公共的内容

​ 2. 为了让所有的类都能使用

含义:

​ 当 A 构造函数的实例, 能够使用 B 构造函数的 属性(构造函数体内)和方法(构造函数原型)

​ 则称 A 构造函数(类) 继承自 构造函数B(类)

​ A 构造函数(类) 叫做 子类

​ B 构造函数(类) 叫做 父类

原型继承

说到底,JS其实是一门介于面向对象和面向过程之间的编程语言。在JS发展初期,为实现面向对象编程的特点(继承),于是网络中的各路大神纷纷开始JS继承的试炼之路。

JS发展最红火的那几年,第一种较为流行的继承方式就是原型继承

核心:将子类的prototype指向父类实例化对象

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender) {
  this.gender = gender
}

// 实现继承
Student.prototype = new Person('Rose', 18)
const s = new Student('男')
console.log(s)

优点:子类能够继承父类的所有属性和方法

缺点:继承自父类的属性不可修改,且由于子类的prototype指向了父类实例对象,导致子类没有了自己的原型(prototype),即子类不能有自己的私有方法

借用继承

有了前者的原型继承,在其基础上,为了弥补其缺点,又出现了另一种继承方式——借用继承

核心:利用call或者applay强行改变父类的this指向

借用继承中已经完美继承到了父类的构造函数体内的属性,但是无法继承到父类原型上的方法

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
  // this.sayBey = function(){console.log('bey bey');}
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {

//在子类中调用父类Person,将Person当做普通函数看待
//Student函数内调用Person函数---Person的this指向Window

//使用call,改变Person的this指向
//子类看做自定义构造函数---this指向new Student时的实例化对象
//Person函数内的this指向了当前实例化对象
//将Person函数内(仅仅写在函数内的)的所有成员,插入到Student实例
  Person.call(this, name, age)

  this.gender = gender
}

// 子类自己的原型方法
Student.prototype.study = function () { console.log('学习中...') }
const s = new Student('男', 'Jack', 18)
//子类中除了添加了父类函数中的内容,其他的什么也没有改变
console.log(s)

在这里插入图片描述

优点:子类可以有自己的原型prototype,且每一个从父类继承下来的属性都是自己的独立私有属性

缺点:子类不能继承父类原型(prototype)上的方法

组合继承

既然原型继承和借用继承都有其致命缺点,并且两者刚好互补,于是就出现了第三种继承——组合继承
核心:将 原型继承 和 借用继承 组合使用

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
  // 利用借用继承继承属性
  Person.call(this, name, age)
  this.gender = gender
}

// 利用原型继承继承方法(属性和方法都能继承)
Student.prototype = new Person()

const s = new Student('男', 'Jack', 18)
console.log(s)

在这里插入图片描述
优点:子类能够继承父类的属性(借用)和方法(原型),且继承的属性都是自己独立私有的

缺点:子类没有自己的原型(prototype),调用了两次父类构造函数,中间多套了一环,且多了一套属性

拷贝继承

补充:

in 关键字的作用

​ 语法: ‘key’ in 对象

​ 得到结果: 一个布尔值

  true, 表示该 key 在该对象的原型链上(可能是自己的属性, 也可能是原型上的属性)

​ false, 表示该 key 在该对象的整个原型链上查找不到

hasOwnProperty()

​ 语法: 对象.hasOwnProperty(‘key’)

​ 返回值: 一个布尔值

​ true, 表示该 key 是该对象自己本身的属性

​ false, 表示该 key 不是该对象自己本身的属性

既然组合继承也行不通,另辟蹊径,直接将父类实例对象复制一份放在子类原型上不就行了——第四种继承 拷贝继承
核心:利用 for..in..继承,利用in关键字遍历—把父类实例的所有内容,遍历复制一份拷贝到子类的原型上

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender) {
  this.gender = gender
}
Student.prototype.study = function () { console.log('学习中...') }

// 准备一个父类的实例
const p = new Person('Jack', 18)
// 遍历父类的实例
for (let k in p) {
  Student.prototype[k] = p[k]
}

const s = new Student('男')
console.log(s)

优点:子类能够继承父类的属性和方法

缺点:使用了for..in..遍历,遍历了整个原型链,性能极差,继承的父类属性一致,没有自己的私有属性

组合继承2.0

拷贝继承永远避不开的一个问题是使用了 for…in…遍历,这是最致命的地方,也是不能让圈内人满意的原因。
之后就有了 组合继承2.0版本
核心:结合 借用继承 和 拷贝继承(仅拷贝父类的prototype)的一部分 实现的继承方案
只要有拷贝继承,就避免不了for…in…遍历的问题。故组合继承2.0也被大家诟病。

// 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }

    // 子类
    function Student(gender, name, age) {
      // 利用借用继承继承属性
      Person.call(this, name, age)
      this.gender = gender
    }
    Student.prototype.study = function () { console.log('学习中...') }

    // 子类的原型继承父类的原型
    // 利用 for in 循环只从 父类.prototype 开始遍历
    for (let k in Person.prototype) {
      Student.prototype[k] = Person.prototype[k]
    }

    const s = new Student('男', 'Jack', 18)
    console.log(s)

优点:子类拥有自己的私有属性,能够继承父类的原型方法

缺点:使用了for..in..遍历,遍历了整个原型链,性能极差

寄生继承的发展

后来出现的一种新颖的继承方式——寄生继承

唾弃版

该版本不能称之为寄生继承,但放在这里是因为这个名字是写这个版本的人首次提出来的。——最垃圾的一种继承,完全是文字游戏掩盖了继承的意义
直接在子类的构造函数体内返回父类的实例(这是正常人能提出来的?)

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
  const p = new Person(name, age)
  return p
}

// 看似得到的是子类的实例
// 本质得到的就是一个父类的实例
const s = new Student('', 'Jack', 18)
console.log(s)

寄生继承

经过后人的努力,JS终于摸到了完美继承的门槛,使用第三方构造函数。
思想:利用第三个构造函数去寄生父类,然后利用子类去继承第三方的构造函数

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender) {
  this.gender = gender
}
  1. 第一版

利用原型继承,让第三方构造函数继承父类,然后同样利用原型继承,让子类继承第三方构造函数

// 1. 出现一个第三方构造函数
function Third() {}
// 2. 利用原型继承, 让 Third 当做子类, Person 当做父类
Third.prototype = new Person('Jack', 18)
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
Student.prototype.study = function () { console.log('学习中...') }
  1. 第二版

改变了第三方继承父类的方式,使用复制的方法,让第三方的原型等于父类的原型

//1. 出现一个第三方构造函数
function Third() {}
// 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
Third.prototype = Person.prototype
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
Student.prototype.study = function () { console.log('学习中...') }

寄生式组合继承(玉伯初版)

寄生式组合继承是国内的前端大神玉伯写出来的,牛*

核心思想:在子类中使用借用继承父类——拿到写在父类构造函数体内的属性和方法。然后使用赋值操作,让第三方的原型等于父类的原型,让子类通过原型继承的方式继承第三方构造函数——第三方构造函数上除了父类的原型外什么也没有,故使用原型继承拿到第三方构造函数上的protorype,即是继承了父类的prototype

比较组合继承而言,组合继承中多了一套无用的属性,且没有自己的私有方法,而寄生式组合继承完美的解决了这个问题

寄生式组合继承出版唯一的问题点—占用了一个变量名

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
function Student(gender, name, age) {
  // 利用借用继承继承属性
  Person.call(this, name, age)
  this.gender = gender
}
// 把 寄生继承 和 借用继承 完美结合
function Third() {}
// 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
Third.prototype = Person.prototype
// 3. 利用原型继承, 让 Student 继承 Third
Student.prototype = new Third()
//多写一步,将子类继承来的prototype写上其构造函数为Student---其实不是
Student.prototype.constructor = Student
Student.prototype.study = function () { console.log('学习中...') }

寄生式组合继承(玉伯完善版)

使用自执行函数将第三方构造函数包起来—解决了玉伯初版中多占用一个变量名的问题

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender, name, age) {
  // 利用借用继承继承属性
  Person.call(this, name, age)
  this.gender = gender
}
// -----------------------------------------------
;(function () {
  // 把 寄生继承 和 借用继承 完美结合
  function Third() {}
  // 2. 利用赋值操作, 让 Third 的 原型 等于 父类的原型
  Third.prototype = Person.prototype
  // 3. 利用原型继承, 让 Student 继承 Third
  Student.prototype = new Third()
  Student.prototype.constructor = Student
})()
// -----------------------------------------------
//将子类自己的操作写在自执行函数外面
Student.prototype.study = function () { console.log('学习中...') }

ES6中的类继承

后来官方出来了类继承,使用关键字extends。
使用extends关键字

=> 语法: class 子类 extends 父类 {}
+ 需要用到 super 关键字(超类)
  => 需要在子类的 构造器(constructor) 内调用使用
  => 注意: 需要调用在 this.xxx 的前面

父类可以是构造函数,也可以是类,无所谓

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }

// 子类
// 创建一个继承自 Person 的 Student 类
class Student extends Person {
  constructor (gender, name, age) {
    // super 等价于原先的 借用继承
    super(name, age)
    this.gender = gender
  }

  study () { console.log('学习中...') }
}

const s = new Student('男', 'Jack', 18)
console.log(s)

在这里插入图片描述

其实官方底层extends函数使用的方法就是寄生式组合继承,奈何官方跟你玩赖。。。不承认

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值