js的继承

ES6的继承

class Parent0 {
	constructor (name = 'parents0') {
    this.name = name
  }
}

class Child0 extends Parent0 {
  constructor (type = 'child0') {
    super()
    // 无需定义父类之外额外属性时,constructor和super皆可省略,构造函数会隐式执行。
    this.type = type
  }
}

let s0 = new Child0();
console.log(s0) // {name: "parents6", type: "child6"}

ES5几种的继承方式及其优缺点:

(补充知识)原型链
  1. 构造函数的prototype属性指向哪,由它实例的对象的__proto__属性就指向哪。
  2. 一个对象实例的constructor属性指向哪,不是看它是由哪个构造函数new出来的,而是看它的__proto__指向的对象原型里的constructor存的到底是谁。即实例对象的constructor属性也是从__proto__所指的原型对象上继承来的。(举个例子,正常情况下,构造函数prototype指向哪个对象原型,哪个对象原型就指向它。如果是人为的将构造函数A的prototype指向某个由其它构造函数构造B的实例对象C时,那么并不会改变该实例对象C原本的constructor为B,从而也导致了A构造函数new出来的对象D的constructor也是B。)
  3. 通过构造函数里的new出来的对象实例,构造函数有的属性,每个new出来的对象都各自独立拥有该属性。原型对象上的属性和方法,则是所有实例对象共有的。
1. 借助构造函数实现继承:在子类的构造函数体内执行父类的构造函数。

在这里插入图片描述

// 父类
function Parent1 () {
    this.name = 'parent1';
}
Parent1.prototype.say = function () {};

// 子类
function Child1 () {
	  // 重点:原理都体现在这一句
    Parent1.call(this);
    // 子类自己的属性
    this.type = 'child1';
}
// 对象实例并没有继承父类原型中的属性和方法,调用时将出错
console.log(new Child1(), new Child1().say());// 报错:say is not a function 

原理:在子类的函数体里执行父级的构造函数,同时改变函数运行的上下文环境(也就是this的指向),使this指向Child1这个类,从而导致了父类的属性都会挂载到子类这个类上去,如此便实现了继承。

缺点:只实现了部分继承。只是把父类中的属性继承了,但父类的原型中的属性继承不了。因为say是加在父类的原型上的,这种继承方式只改变父类构造函数在子类函数体中的指向,继承不了原型的属性。

2. 借助原型链实现继承(让子类构造函数的prototype指向父类的实例,从而继承父类的属性及父类原型对象上的属性)

在这里插入图片描述

function Parent2 () {
    this.name = 'parent2';
    this.play = [1, 2, 3];
}
function Child2 () {
    this.type = 'child2';
}
// 重点:通过把Child2的原型指向Parent2的实例来实现继承
Child2.prototype = new Parent2();
// 这样做就使得Child2()对象的实例的__proto__属性变为父类Parent2的实例对象。


var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
// 不增加4,打印结果:[1,2,3],[1,2,3].

// 增加4后
s1.play.push(4);
// 打印结果:[1,2,3,4],[1,2,3,4]

缺点:我们只改了s1这个实例的属性,却发现Child2的其他实例的属性都一起改变了。
原因:s1,s2共用同一个原型对象。即 s1.proto === s2.proto。而该play数组就是在那个实例对象上。s1修改的是它原型的属性,原型的属性修改,所有继承自该原型的类的属性都会一起改变,因此Child2的实例之间并没有隔离开来,这显然不是我们想要的。

3. 组合方式

组合方式就是前两种方法构造函数和原型链组合而成的,这种方式就解决了上面两种方式的不足。
在这里插入图片描述

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
}
function Child3 () {
	  //子类里执行父类构造函数
    Parent3.call(this);
    this.type = 'child3';
}
//子类的原型指向父类
Child3.prototype = new Parent3();

var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
// 打印结果:[1,2,3,4] , [1,2,3]

可以看出,修改某个实例的属性,并不会引起父类的属性的变化。这种方式的继承把构造函数和原型链的继承的方式的优点结合起来,并弥补了二者的不足,功能上已经没有缺点了。
但是每一次创建实例,都会执行两次构造函数这是没有必要的,因为在继承构造函数的时侯,也就是Parent3.call(this)的时候,parnet的属性已经在child里运行了,外面原型链继承的时候就没有必要再执行一次了。
缺点
1)因为创建一个子类的实例的时候,父类的构造函数执行了两次。
2)可以看到s3是new Child3()出来的,但是他的constructor却是Parent3.

4. 组合方式的优化(解决继承方法3的第一个缺点)

在这里插入图片描述
上面一种继承方式问题出在继承原型的时候又一次执行了父类的构造函数,共两次执行了父类的构造函数。所以优化就从这一点出发。
组合方式中为了解决借助构造函数继承(也就是本文中第一种)的缺点,父类的原型中的属性继承不了,所以才把子类的原型指向了父类实例。
但是父类的属性,在子类已经中已经存在了,子类只是缺少父类的原型中的属性,所以,根据这一点,我们做出优化。

function Parent4 () {
    this.name = 'parent4';
    this.play = [1, 2, 3];
}
function Child4 () {
    Parent4.call(this);
    this.type = 'child4';
}
// 子对象原型引用父对象原型
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5, s6);

console.log(s5 instanceof Child4, s5 instanceof Parent4);// true true
console.log(s5.constructor);// f Parent4(){this.name = 'parent4';this.play = [1,2,3];}

在这种继承方式中,并没有把直接把子类的原型指向父类,而是指向了父类的原型。这样就避免了父类构造函数的二次执行,从而完成了针对组合方式的优化。但还是有一点小问题,输出结果中,可以看到s5是new Child4()出来的,但是他的constructor却是Parent4.

缺点:即由这种方法创造出的子类的实例对象无法区分它到底是由子类实例化的还是由父类实例化的。
原因:这是因为Child4这个类中并没有构造函数属性,它的构造函数是从原型链中的上一级拿过来的,也就是Parent4。

5.组合的完美优化(共3步)(解决继承方法3的两个缺点)

在这里插入图片描述

function Parent5 () {
  this.name = 'parent5';
  this.play = [1, 2, 3];
}

function Child5 () {
  //1.子类的构造函数里调用父类的构造函数
  Parent5.call(this);
  this.type = 'child5';
}

//2.把子类的原型指向通过Object.create创建的中间对象
Child5.prototype = Object.create(Parent5.prototype);
//3.把Child5的原型的构造函数指向自己
Child5.prototype.constructor = Child5;

//测试
var s7= new Child5();
console.log(s7 instanceof Child5, s7 instanceof Parent5)// true true
console.log(s7.constructor);// f Child5(){}

通过把子类的原型指向Object.create(Parent5.prototype),实现了子类和父类构造函数的分离,但是这时子类中还是没有自己的构造函数,所以紧接着又设置了子类的构造函数,由此实现了完美的组合继承。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值