【JS】中的继承(包括ES6)

目录

一、原型链继承

1. 基本思想

2. 实现方法

3. 存在的问题

4. 优点与不足

二、借用构造函数继承

 2. 实现方法

3. 存在的问题

4. 优点与不足

三、组合式继承

1. 基本思想

2. 实现方法

3. 存在的问题 

4. 优点和不足

四、寄生组合继承

1.实现方法

2. 存在的问题

 4. 优点和不足

五、ES6 中的继承

1.实现方式


一、原型链继承

1. 基本思想

原型链继承的基本思想是通过原型来继承多个引用类型的属性和方法

 实现的基本思路是利用构造函数实例化对象,通过 new 关键字,将构造函数的实例对象作为子类函数的原型对象。

2. 实现方法

// 定义父类函数
function Father() {
    // 定义父类属性
    this.name = 'father'
}
// 给父类的原型添加方法
Father.prototype.say = function () {
    console.log('我是爸爸');
}
// 创建子类函数
function Son() {}
// 实现继承
Son.prototype = new Father()
// 打印参考
console.log(Son.prototype) // Father {name: "father"}

解释:首先定义了一个父函数和子函数,添加了一些属性和方法

而实现继承的关键在于 Son.prototype = new Father() 。那它怎么理解呢

首先我们需要了解一下 new 操作符的执行过程

  1. 创建一个空对象
  2. 继承函数原型,将这个新对象的 __proto__ 属性赋值为构造函数的原型对象
  3. 构造函数内部的 this 指向新对象
  4. 执行函数体
  5. 返回这个新对象

new 的过程后,我们可以知道当我们在 new Father() 操作时,这一步将 Father 构造函数的原型对象打包给了 Father 的实例对象,也就是 father.__proto__ = Father.prototype,换到这里也就是 Son.prototype.__proto__ = Father.prototype,这样一来也就是将父类的实例对象作为了子类的原型,这也一来就在子类与父类实现了连接

关键性代码:son.prototype = new Father()

3. 存在的问题

在这个例子中:

function Father() {
	// 定义父类属性为引用数据类型
	this.a = [1, 2, 3, 4]
}

我们将上面的代码中 a 的值改成引用数据类型,我们知道对于引用数据类型只会保存对它的引用,也就是内存地址。

我们先创建两个继承这个父类的子类 son1 son2

let son1 = new Son()
let son2 = new Son()

接着我们想向 son1 中的 a 数组添加一个值 5 ,我们会这么操作

son1.a.push(5)

打印一下此时的son2 中a 数组也被改变了,而这就是原型链继承方式带来的引用数据类型被子类共享的问题

4. 优点与不足

优点:

  • 父类的方法可以复用
  • 操作简单

缺点

  • 对于引用数据类型数据会被子类共享,也就是改一个其他都会改
  • 创建子类实例时,无法向父类构造函数传参,不够灵活。

二、借用构造函数继承

为了解决原型链继承方式带来的引用值无法共享的问题,从而兴起了一种“盗用构造函数继承”的方式

1. 基本思想

为了想要实现引用值共享的问题,我们就不能给子类直接使用原型对象上的引用值。

因此,可以在子类构造函数中调用父类构造函数。

function Son() {
	this.a = [1, 2, 3, 4]
}

如果我们将子类的代码改写成这样,当我们通过 Son 构造函数实例化实例对象时,每个实例对象中变量 a 都是独立的,属于自身的,当我们修改一个时,不会影响另一个的值

 2. 实现方法

function Father() {
    this.a = [1, 2, 3, 4]
}

function Son() {
    Father.call(this)
}
let son1 = new Son()
let son2 = new Son()
son1.a.push(5)
console.log(son1, son2)

我们可以看到,在上面的实现方式中,并没有直接采用 this.a... 而是采用了 Father.call(this)。

我们原先直接将 this.a 直接的写在了子类函数里面,这和直接在子类中调用 Father 方法是类似的,唯一的差别就是 this 指向问题。

如果直接的在子类中调用 Father() ,那么它的 this 将指向 window ,这样就无法将数据绑定到实例身上,因此我们需要改变 this 的指向,指向当前的子类构造函数这样一来就能将数据绑定到了每个实例对象身上。

同时由于我们的关键语句采用的是 call,因此我们可以给父类构造函数传递参数,实现传递参数 

3. 存在的问题

如果在父类构造函数的原型上添加方法,则会出现以下问题:

Father.prototype.say = function () {
    console.log(111);
}

即:无法在子类上找到 say 方法

4. 优点与不足

优点

  • 解决了无法共享引用值的问题
  • 能够传递参数

缺点

  • 只能继承父类的实例属性和方法,不能继承父类的原型属性和方法
  • 父类方法无法复用。每次实例化子类,都要执行父类函数。重新声明父类所定义的方法,无法复用。

三、组合式继承

在前面两种方法中,都存在着一定的缺陷,所以很少会将它们单独使用。为此一种新的继承方式就诞生了:组合继承(伪经典继承),组合继承结合了原型链与盗用构造函数继承的方式,将两者的优点结合在一起。

1. 基本思想

通过原型链继承方式继承父类原型上的属性和方法,再使用盗用构造函数的方式继承实例上的属性

这样,实现了把方法定义在原型上以实现复用,又保证了让每个实例都有自己的属性

2. 实现方法

function Father() {
    this.a = [1, 2, 3, 4]
}
Father.prototype.say = function () {
    console.log(111);
}
function Son() {
    Father.call(this)
}
Son.prototype = new Father()
let son1 = new Son()
let son2 = new Son()

 其实只是在盗用构造函数的基础上添加了原型链继承的关键性代码

Son.prototype = new Father()

在上面的代码中,通过盗用构造函数的方法继承了父类实例上的属性 a ,通过原型链的方式,继承了父类的原型对象

3. 存在的问题 

 打印一下 son1son2

我们将 Father 的实例绑定在了 Son 的原型上,但是我们又通过盗用构造函数的方法

Father 自身的属性手动添加到了 Son 的身上,因此在 Son 实例化出来的对象上,会有一个 a 属性,原型上也会有一个 a 属性

4. 优点和不足

优点:

  • 解决原型链继承中属性被共享的问题
  • 解决借用构造函数解决不能继承父类原型对象的问题

缺点:

  • 调用了两次的父类函数,有性能问题
  • 由于两次调用,会造成实例和原型上有相同的属性或方法

四、寄生组合继承

通过寄生的方式来修复组合式继承的不足,完美的实现继承

1.实现方法

在组合继承的方法中我们 call 了一次,又 new 了一次,导致调用了2次父类,而在寄生式继承中,我们可以调用 API 来实现继承父类的原型

我们将两者结合在一起

不再采用 new 关键字来给改变原型

function Father() {
    this.a = [1, 2, 3, 4]
}
Father.prototype.say = function () {
    console.log(111);
}
function Son() {
    Father.call(this)
}
Son.prototype = Object.create(Father)
let son1 = new Son()
let son2 = new Son()

采用 Object.create 来重写子类的原型,这样就减少了对父类的调用

这时我们在控制台打印 son1 会发现问题解决了

2. 存在的问题

在这种方法中,同样存在着一些问题,当我们的子类原型上有方法时

会因为原型被重写而丢失了这些方法

我们在代码最上方添加上一个 sayHi 方法

Son.prototype.sayHi = function() {
	console.log('Hi')
}

 

 要想解决这个问题,其实可以在原型被重写之后再添加子类原型的方法

 4. 优点和不足

优点:

  • 基本上是最佳的继承方案了,当然还有圣杯继承
  • 只调用了父类构造函数一次,节约了性能。
  • 避免生成了不必要的属性

缺点:

  • 子类原型被重写

五、ES6 中的继承

由于 ES6 之前的继承过于复杂,代码太多,再 ES6 中引入了一种新的继承方式 extends 继承

采用 extends 关键字来实现继承

1.实现方式

class Father {}
class Son extends Father {
    constructor() {
        super()
    }
}

这样就实现了子类继承父类,这里的关键是需要在子类的 constructor 中添加一个 super 关键字

 需要注意的是

子类中constructor方法中必须引用super方法,否则新建实例会报错,这是因为子类自己的 this 对象,必须先通过父类构造函数完成塑性,得到父类的属性和方法;

然后再加上子类自己的属性和方法;

如果没有 super 方法,子类就没有 this 对象,就会报错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿昊在

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

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

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

打赏作者

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

抵扣说明:

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

余额充值