为了账号安全,请及时绑定邮箱和手机立即绑定

ES6中的类继承和ES5中的继承模式详解

标签:
JavaScript

1、ES5中的继承模式

我们先看ES5中的继承。

既然要实现继承,首先我们得要有一个父类。

复制代码

Animal.prototype.eat = function(food) {
    console.log(this.name + '正在吃' + food);            
}function Animal(name) {    this.color = ['green','red','blue'];    this.name = name || 'animal';    this.sleep = function() {
        console.log(this.name + "正在睡觉")
    }
}

复制代码

1.1、原型链继承

原型链继承核心: 将父类的实例作为子类的原型。

复制代码

function Cat(name) {    this.name = name    this.color = ['green','red','blue'];//引用类型值,,所有实例会共享这个属性。}
Cat.prototype = new Animal();var cat = new Cat('cat');
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat instanceof Animal);
console.log(cat.sleep());

复制代码

原型链式继承模式实现了子类对父类的原型的继承。

但是,原型链式继承并没有实现代码的复用,一些共同的属性:如name,在子类中还是得重新写一遍(即同一套代码还是得重新写)。

再者,cat继承了Animal实例的所有属性和方法,这些方法并不都是我们需要的,也就是过多的继承了没有用的属性。且如果原型中包含引用类型值,那么所有的实例会共享这个属性。

1.2、构造函数模式

构造函数模式核心: 在子类型构造函数的内部调用超类型构造函数。

复制代码

function Person(name,age,sex){    this.name = name;    this.age = age;    this.sex = sex;
}function Student(name,age,sex){
    Person.call(this,name,age,sex);    this.grade = grade;
}
let student = new Student;

复制代码

优点:

  • 构造函数模式继承实现了代码的复用

缺点:

  • 不能继承借用的构造函数的原型,只能借用构造函数本身的属性和方法

  • 每次构造函数都要多走一个函数

1.3、组合继承

实现核心:组合继承结合了上面两种方式的继承模式,即实现了代码复用,也实现呢了原型继承。

复制代码

function SuperType(name) {    this.name = name;    this.colors = ['red','blue','pink'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}function SubType(name,age) {    //继承属性
    SuperType.call(this,name);//在创建实例时第二次调用SuperType
    this.age = age;
}//继承方法SubType.prototype = new SuperType();//第一次调用SuperTypeSubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    console.log(this.age)
}

复制代码

缺点:

  • 父类构造函数被调用2次,子类实例的属性存在两份,一份在原型上,一份在实例属性上。造成内存的浪费。

1.4、寄生组合式继承

寄生组合式继承是对组合继承的进一步优化。我们先看一下为什么要写这个语句。

SubType.prototype = new SuperType();

我们无非是想让SubType继承SuperType的原型。但是我们为什么不直接写成这样呢?

SubType.prototype = SuperType.prototype

这样写确实可以实现子类对象对父类对象原型的继承。但是这样写的话:所有继承该父类的子类对象的原型都指向同一个了。也就是说Target不能有自己的原型了。这显然不是我们想要的。

既然不能直接继承,那可不可以间接继承SuperType.prototype呢。这就是最终的解决方案:寄生组合式继承。

我们让一个函数去指向SuperType.prototype,然后让SubType.prototype指向这个函数产生的对象不就可以了嘛。

复制代码

function inherit(Target,Origin) {//实现寄生组合式继承的核心函数    function F() {};
    F.prototype = Origin.prototype; //F()的原型指向的是Origin
    Target.prototype = new F(); //Target的原型指向的是F()
    Target.prototype.constructor = Target; 
    SubType.prototype.__proto__ == SuperType.prototype
}function SuperType(name) {    this.name = name;    this.colors = ['red','blue','pink'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}function SubType(name,age) {    //继承属性
    SuperType.call(this,name);//在创建实例时第二次调用SuperType
    this.age = age;
}

inherit(SubType,SuperType);//实现寄生组合式继承

复制代码

我们再来看一下实现寄生组合式继承的核心函数。F函数其实是通用的,我们没必要每次进入inherit函数时都声明一遍。所以我们可以用闭包的形式来写:

复制代码

var inherit = (function () {        var F = function () {};        return function (Target , Origin) {
            F.prototype = Origin.prototype;//F()的原型指向的是Origin
            Target.prototype = new F();//Target的原型指向的是F()
            Target.prototype.constructor = Target;
            Target.prototype.uber = Origin.prototype;
            SubType.prototype.__proto__ == SuperType.prototype
        }
    })()

复制代码

2、ES6中的类继承

我们先来看一下ES6里面是如何定义一个类的。

复制代码

class Parent {
    constructor(name,age) {        this.name = name;        this.age = age;
    }
    
    getName() {        return this.name;
    }
}
typeof Parent; //function,类的数据类型就是函数,类本身就指向构造函数

复制代码

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Parent,对应ES6的Parent类的构造方法。
Parent类除了构造方法,还定义了一个getName方法。

注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了 就可以了。另外,方法之间不需要逗号分隔,加了会报错。类中定义的所有方法都是不可枚举的。

此外:类的所有方法都定义在类的prototype属性上面。且class不存在变量提升,如果在class声明之前调用,会报错。

复制代码

new Parent();
class Parent {
    constructor(name,age) {        this.name = name;        this.age = age;
    }
    
    getName() {        return this.name;
    }
}//Uncaught ReferenceError: Parent is not defined

复制代码

2.1、类的constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,

如果没有显式定义,一个空的constructor方法会被默认添加。

2.2、类的继承

复制代码

class Child extends Parent {
    constructor(sex) {
        super();        this.sex = sex;
        
    }
}var child = new Child('xiaoyu',12,'man');

复制代码

在子类的构造函数中,如果显示声明了constructor,则必须要显示的调用super函数(这一点和Java有点不一样)。

只有调用super之后,才可以使用this关键字,否则会报错。

2.3类的prototype属性和__proto__属性

大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。

Class作为构造函数的语法糖,同时有 prototype属性和__proto__属性,因此同时存在两条继承链。

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类。

  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

这些特点和ES5的寄生组合式继承完全一致,所以类的继承可以看做是寄生组合式继承的语法糖(个人理解)。因为阮一峰大大的书籍《ES6入门基础》里面认为ES6的继承机制完全和ES5的继承机制不同。阮一峰大大的书是这么说的。

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

所以对这方面有深入理解的小伙伴们可以说说自己的理解。共同进步。

先写到这里,后面如果有更多的理解再继续添加。

原文出处:https://www.cnblogs.com/yuliangbin/p/9469709.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消