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


    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();//第一次调用SuperType
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function() {
        console.log(this.age)
    }

    缺点:

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

    1.4、寄生组合式继承

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

    SubType.prototype = new SuperType();

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

    SubType.prototype = SuperType.prototype

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

    既然不能直接继承,那可不可以间接继承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。

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

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

  • 相关阅读:
    JSON2 源代码
    C#冒泡排序详解
    SqlHelper 带详细中文注释
    js-cookie
    淘宝镜像(cnpm) 安装
    vue全局刷新
    webpack-npm安装-查看当前版本
    半环进度条
    Parameter 'name' implicitly has an 'any' type.
    vue3.0-如何切换路由-路由模式ts
  • 原文地址:https://www.cnblogs.com/yuliangbin/p/9469709.html
Copyright © 2020-2023  润新知