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。

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

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

posted @ 2018-08-13 17:34  余大彬  阅读(2079)  评论(0编辑  收藏  举报