JavaScript中的继承

349 阅读5分钟

前言

提起继承我们最开始想到了应该是子类继承父类,接下来尽量用简洁的语言来表达清楚什么是继承

正文

原型继承

让子类的原型Child.prototype等于父类的实例new Parent

什么是原型继承

function Parent(){
	this.dd = 'qq';
}
Parent.prototype.getDd = function(){
	return this.dd
}

function Child(){
	this.name = 'qq';
}

Child.prototype.getName = function(){
	return this.name
}
let child = new Child

此时思考一下child能获取或调用哪些方法或属性?答案肯定是可以获取name属性,以及可调用getName方法,所以我们能够发现,child实例可调用自己Child类私有的以及Child类原型prototype上公有的方法

那么此时我们想让child继承父类Parent的属性,我们只需要将Child.prototype=new Parent,因为new ParentParent的实例,可以调用自己类私有以及类原型上的公有方法。这样我们便达到了原型继承的目的。

function Parent(){
	this.dd = 'qq';
}
Parent.prototype.getDd = function(){
	return this.dd
}
function Child(){
	this.name = 'qq';
}
Child.prototype = new Parent;
Child.prototype.getName = function(){
	return this.name
}
let child = new Child;

这样我们创建的实例child便可以调用getDd以及child.dd,但是我们也要注意代码添加的位置,如果我们在倒数第二行添加,则会导致getName属性消失。

接下来我们在控制台输出child,打印结果如下图,可能你会对打印结果存在疑惑,为什么是这样一层一层包下去的呢?接下来我们解释一下结果的产生:

原型链如下图所示,我们能够看到蓝色的①②③,这就是执行时原型链的查找顺序:

  • 首先在①处,存在属性name: 'qq'

  • 接下来按照②child.__proto__方向进行查找,找到属性dd: 'qq', getName: function ...

  • 继续按照③new Parent.__proto__方向查找,找到Parent.prototype里面的属性constructor, getDd

  • ...

这就是child的结果的产生。

总结

  • 父类中私有和公有的属性方法,最后都变为子类实例公有的

  • 和其他语言不同的是,原型继承并不会把父类的属性方法【拷贝】给子类,而是让子类实例基于__proto__ 原型链找到定义的属性和方法,"指向/查找"方式的

  • 在上面的实例child中,通过child.__proto__.xxx = xxx来修改子类原型(原有父类的一个实例)中的内容,内容被修改后,对子类的其他实例有影响,但是对父类的其他实例不会有影响

  • 通过child.__proto__.__proto__.xxx = xxx直接修改的使父类原型,这样不仅会影响父类的其他的实例,也会影响其他子类的实例

CALL继承

什么是CALL继承

对于call我们能够想到的是改变this指向,并且最常见的使用是A.call(B),将A执行,并强制将this改变为B,所以接下来也将使用这种方式来理解CALL继承。

首先我们要先明白一件事,在类中this指向的是实例,也就是说在Childthis为实例child,所以执行的this.name是在实例child上添加私有属性。所以我们看下面的代码:

function Parent(){
    this.dd = 'qq';
}
Parent.prototype.getDd = function(){
    return this.dd
}

function Child(){
    Parent.call(this);
    this.name = 'qq';
}

Child.prototype.getName = function(){
    return this.name
}
let child = new Child

我们添加了 Parent.call(this);这样一行代码,在子类中调用父类执行并强制将Parent中的this修改为子类的实例对象child,此时Parent执行时this.dd = 'qq'相当于给实例child上添加属性dd.相当于让子类的实例继承了父类的私有的属性,并且也变为了为子类私有的属性"拷贝式"

总结

CALL继承只能继承父类中私有的,不能继承父类中公共的

寄生组合继承(CALL继承 + 另类原型继承)

根据上面的两种继承,第一种实现了将父类的私有以及公有属性都继承了,对于子类实例讲,父类的私有属性却变成了公有有。第二种便只能实现私有属性的继承,将父类的私有属性变为子类实例的私有属性。而我们最想要的是:将父类的私有属性,变为子类的私有属性(这个使用CALL继承已经实现),将父类的公有属性,变为子类实例的公有属性。

实现思路:在CALL继承的基础上实现将父类的共有属性,变为子类实例的公有属性

上图为CALL继承的原型链,我们能够发现现在只需要对Child.prototype.__proto__Parent.prototype之间建立连接就可以实现,所以我们的完整代码为:

function Parent(){
	this.dd = 'qq';
}
Parent.prototype.getDd = function(){
	return this.dd
}

function Child(){
    Parent.call(this);
	this.name = 'qq';
}
Child.prototype.__proto__ = Parent.prototype

Child.prototype.getName = function(){
	return this.name
}
let child = new Child

根据上图说明我们的效果已经实现了,但是由于在低版本浏览器中对__proto__的兼容性不好,所以我们在进行优化一下:

function Parent(){
	this.dd = 'qq';
}
Parent.prototype.getDd = function(){
	return this.dd
}

function Child(){
    Parent.call(this);
	this.name = 'qq';
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child;
Child.prototype.getName = function(){
	return this.name
}
let child = new Child

说明:我们将Child.prototype的指向改变了,可以将Object.create(Parent.prototype)理解为创建了一个空对象,然后将这个空对象.__proto__指向了Parent.prototype,但是这时候子类的原型上丢失了constructor属性,所以我们手动添加此属性,保存完整性。所以实现了将父类的共有属性,变为子类实例的公有属性。

ES6中的类和继承

class Parent {
    constructor(){
        this.x = 100; 
    }
    getX(){
        return this.x
    }
}

class Child {
    constructor(){
        this.y = 100; 
    }
    getY(){
        return this.y
    }
}
let c1 = new Child;

说明:

  • ES6中,通过class创建类,其中constructor相当于类原型上的constructorgetX相当于在类的原型上添加属性。

  • 想要实现继承需要在上面的例子中添加以下代码

    class Child extends Parent{
      constructor(){
          super();
          this.y = 100; 
      }
      getY(){
          return this.y
      }
    }
    

    ①. extends表示继承,后面的表示Parent继承谁的属性。

    ②. 当使用继承时一定要在constructor中加入super,相当于我们之前的CALL继承,super执行就等同于把Parent中的constructor执行

最后

biu biu biu ~ ❤️❤️❤️❤️ 点关注 🙈🙈 不迷路 点个赞哦😘