【javascript】es5es6类的继承详细总结

背景

  • 以前也听过什么对象冒充继承,什么原型继承的,感觉还是太乱,特此总结一下。

ES5继承

一、私有属性继承

function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}
function Tiger(){
    Animal.call(this)//继承调用父类函数,把this指向改为子类
}
let babyTiger = new Tiger()

console.log(babyTiger.name);
//babyTiger.say()报错无法得到
  • 先说一下私有属性继承,就是大家说的对象冒充继承,用New的话,就相当于copy一个对象,对象里也有类里的私有方法 ,那么如何让子类继承父类的私有属性?那就让子类也搞个跟父类一样的私有即可。子类用new Tiger,但Tiger里面不能直接调用Animal()因为,Animal里的this指向是global,如果这么调相当于把name挂到global上。
function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}
function Tiger(){
    Animal()//这么调用会挂到global上
}
let babyTiger = new Tiger()
console.log(name);//yehuozhili
console.log(babyTiger.name);//undefined
  • 所以使用call改变this指向,Animal里的this就指向子类实例了。这样子类实例也能打印name了。

二、原型方法继承

第一种方法

  • 再说下原型方法继承,就是前面在Animal.prototype上加的say方法。
  • 这个原型方法继承本质就是沿着__proto__去找有没有这个方法。
  • 而原来的Animal的实例本来也没这个方法,沿着__proto__找到了Animal.prototype找到了这个方法,所以才能调用。
  • 那么我们只要让Tiger的实例沿着__proto__去寻找就行了,Tiger的实例的__proto__本来是指向其类Tiger.prototype,但是Tiger.prototype是js引擎自己生成的,并没有和Animal有啥关系,所以需要让Tiger.prototype.__proto__=Animal.prototype
function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}
function Tiger(){
}
let babyTiger = new Tiger()
Tiger.prototype.__proto__=Animal.prototype
babyTiger.say()

第二种方法

function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}
function Tiger(){
}
Tiger.prototype=Object.create(Animal.prototype)//注意顺序必须在子类生成实例前
Tiger.prototype.constructor=Tiger
let babyTiger = new Tiger()
babyTiger.say();
  • 这个Object.create方法实质是新建个function,然后js会生成这个function的prototype,让这个prototype等于传入参数,也就是Animal的prototype,然后返回这个function的实例,这个function的实例沿着__proto__就能找到Animal原型链上的方法,返回的这个实例等于Tiger.prototype,也就是说Tiger的实例也能沿__proto__一直找到Animal上的方法了。最后还有个地方不一样,就是要改变这个function实例的constructor,因为Tiger的实例没有constructor,往上找是Tiger的prototype也就是我们做的这个function的实例,这个实例已经改掉了,就是function.prototype改给了Animal.prototype,所以本来指向fn.prototype的指向了Animal.prototype,于是需要让fn实例的constructor指向Tiger。
function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}
function Tiger(){
}
function create(parentprototype){
    let fn = function(){}
    fn.prototype=parentprototype
    let sfn = new fn;
    sfn.constructor= Tiger//不加这句就是Animal
    return sfn
}

Tiger.prototype=create(Animal.prototype)//注意顺序必须在子类生成实例前
let babyTiger = new Tiger()
babyTiger.say();
console.log(babyTiger.constructor);//Tiger

第三种方法

  • 第三种方法就是网上常说的new一个父类实例,然后改造这个父类实例变为子类prototype
function Animal(){
    this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
    console.log('say');
}

function Tiger(){
    Animal.call(this)
}
Tiger.prototype=new Animal()
Tiger.prototype.constructor=Tiger
let babyTiger=new Tiger()
babyTiger.say()
console.log(babyTiger.constructor);
  • 这个方法跟上面那个方法差不多,找了Animal的实例替代Tiger的prototype,所以还需要把constructor也替换掉,不然打印babyTiger.constructor打印的是Animal。

  • 所以看来这三种方法里,最推荐的是第一种方法,因为第一种方法不需要设定constructor了,本来就是Tiger生成的。后面两种方法都需要再去设置下constructor。而且第一种方法是最符合大众对于继承的逻辑认知的。
    三、静态方法继承

  • 这个网上很多地方都没讲到,可能大家一开始不在意这个继承,es6语法子类extend原来父类,父类的静态方法一样被继承到子类的。

  • 静态方法的本质是加在父类上,而不是加在父类原型上,所以根据原型链原则,只要把子类的__proto__变为父类即可继承。

function Animal(){
    this.name = 'yehuozhili'
}
Animal.ppp=function(){
    console.log('pp');
}
function Tiger(){
    Animal.call(this)
}
Tiger.__proto__=Animal
Tiger.ppp()//pp
  • 所以ES5要完美继承得把这3方法个继承全部写上才ok,网上很多地方就只继承私有属性和原型链。

ES6继承

一、普通继承

  • es6继承比较简单,一个extend就可以继承到了:
class Animal{
    constructor(){
        this.name = 'yehuozhili'
    }
    say(){
        console.log('say');
    }
    static ppp(){
        console.log('ppp');
    }
}
class Tiger extends Animal{
}
let babyTiger = new Tiger();
console.log(babyTiger.name);
babyTiger.say()
Tiger.ppp()
  • 一个extend不管是私有还是原型还是静态全部搞定,真的是比es5省力太多。

二、传参继承

  • es6的传参操作方法就跟es5有点不一样,主要是有个super的东西。
  • 如果子类不写constructor,那么就会找父类的constructor。
  • 但是如果子类写了constructor,那么如果需要传参,必须写super。不写constructor相当于它隐式调用了前面继承私有属性的Animal.call(this),但是也无法在constructor里面加上Animal.call(this),会报错,只能用super。
class Animal{
    constructor(name){
        this.name = name
    }
    say(){
        console.log('say');
    }
    static ppp(){
        console.log('ppp');
    }
}
class Tiger extends Animal{
    constructor(name){
        //this.name=name  错误写法,会报错
        //Animal.call(this,name) 错误写法,会报错
        super(name)
    }
}
let babyTiger = new Tiger('yehuo');
console.log(babyTiger.name);
  • 有人会问super到底是个啥?从源码里看会发现这段:
function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ?
        Object.getPrototypeOf : function _getPrototypeOf(o) {
            return o.__proto__ || Object.getPrototypeOf(o);
        };
    return _getPrototypeOf(o);
}
_getPrototypeOf(Tiger).call(this, name)
  • 这个_getPrototypeOf这个方法就是个寻找__proto__的函数,如果这个东西有Object.getPrototypeOf方法,那么就通过这个方法去寻找,否则就直接去找它的__proto__。貌似是因为现在浏览器一般访问原型速度都比较慢,从性能上看用Object.getPrototypeOf方法去找原型性能比较好。
  • 所以这个super本质是找传入的子类的__proto__然后进行调用,在这个地方就相当于替换成Animal.call(this)
  • 但是实际上super位置不同,代表的也不一样。
  • 如果能理解上面这个_getPrototypeOf相信下面的super指谁都能一眼看出。
class Animal{
    constructor(name){
        this.name = name
    }
    say(){
        console.log('say');
        return 300
    }
    static ppp(){
        console.log('ppp');
        return 400
    }
}
class Tiger extends Animal{
    constructor(name){
        super(name)//指Animal
    }
    static a(){
       super.ppp()//指Animal
    }
    say(){
        super.say()//指Animal.prototype
    }
    static get fff(){
       console.log(super.ppp());//指Animal,但是其中的this指向为Tiger
    }
}
Tiger.a()//ppp
let babayTiger = new Tiger()
babayTiger.say()//say
Tiger.fff//ppp 400
  • 最值得注意的就是这个static get fff函数,这个函数为什么this指向变了?源码是这么写的:
function _get(target, property, receiver) {
    if (typeof Reflect !== "undefined" && Reflect.get) {
        _get = Reflect.get;
    } else {
        _get = function _get(target, property, receiver) {
            var base = _superPropBase(target, property);
            if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property);
            if (desc.get) { return desc.get.call(receiver); } return desc.value;
        };
    } return _get(target, property, receiver || target);
}
 _createClass(Tiger, [{
           key: "say",
           value: function say() {
               _get(_getPrototypeOf(Tiger.prototype), "say", this).call(this); 
           }
       }], [{
           key: "a",
           value: function a() {
               _get(_getPrototypeOf(Tiger), "ppp", this).call(this);
           }
       }, {
           key: "fff",
           get: function get() {
               console.log(_get(_getPrototypeOf(Tiger), "ppp", this).call(this))
           }
       }]);
  • 这个_get函数,实际上就是看你有没有Reflect,然后调用Reflect.get,如果没有它就自己造一个。而_getPrototypeOf前面已经解读过了,就是找__proto__,而这里的this,因为是在Tiger里进行的,所以this是Tiger,实际上相当于console.log(Reflect.get(Animal,'ppp',Tiger))
  • 所以,这时候super指的还是父类,但是this变了,所以取不到私有属性,但是静态方法跟this无关,所以可以拿到静态方法。

三、抽象类

  • 抽象类就是不给new这个类,所有别的类继承它,继承的类可以new。
  • 实际上就是写这个类的时候加一个判断。
  • 上一篇写的是es5类表示es6里可以看到不给用类自己调用使用的是instaceof,取到this后然后instanceof进行判断。
  • 这里就不能用这个方法了,因为new出来的都是父类的实例,所以都是的,这里有2种方法:
class Animal{
    constructor(name){
        console.log(new.target);
        console.log(this.constructor);
        this.name = name
        if(new.target===Animal){
            throw new Error('forbid new')
        }
    }
}
class Tiger extends Animal{
}
new Animal;
new Tiger
  • 一个就是打印new.target,一个就是打印this.constructor解决。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

业火之理

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

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

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

打赏作者

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

抵扣说明:

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

余额充值