记录寄生继承和ES6 Class继承

967 阅读4分钟

寄生组合继承

这是一种接近完美(接下来会展开)的继承方式,先来看其它一些继承方式有助于记忆;

原型链继承
function Parent(){
    this.name = 'parent';
    this.colors = ['red','orange','green'];
};
Parent.prototype.skill = 'run';

function Child(){
    this.name = 'child';
};
Child.prototype = new Parent();

let child1 = new Child();

这就是原型链继承,

  • 优点:继承了父类的模板和原型
  • 缺点:
  1. 子类修改了父类的引用类型的变量,会影响到所有的子类;
  2. 创建子类时,无法向父类构造函数传参

关于缺点一 顺便来看下赋值和引用

let arr1 = ['red','orange','yellow'];
let arr2 = ['张三','李四','老王'];
let copyArr1 = arr1;
let copyArr2 = arr2;
copyArr1.push('green');
copyArr2 = ['张三','老王'];
console.log(arr1);        //  (4) ["red", "orange", "yellow", "green"]
console.log(arr2);        //  (3) ["张三", "李四", "老王"]

第一个是引用,第二个是赋值,给予了copyArr2 一个新的内存空间和原来的arr2无关了;

构造函数继承
function Parent(name){
    this.name = name;
    this.colors = ['red','orange','green'];
};
Parent.prototype.skill = 'run';

function Child(){
    Parent.call(this,'我是parent');
    this.name = 'child';
};
let child1 = new Child();
/*
    Child {name: "我是parent", colors: Array(3), say: "Hi!"}
*/
console.log(child1); 
console.log(child1.skill);  //  undefined

构造函数继承总结

  • 优点
  1. 实例子类的时候可以扩展
  2. 可以传参
  3. 子类修改继承而来的引用类型的值不会影响父类
  • 缺点
  1. 无法继承父类的原型
  2. 无法实现父类函数的复用,有多少个子类就需要调用多少次父类构造函数;
组合继承
function Parent(name){
    this.name = name;
    this.colors = ['red','orange','green'];
};
Parent.prototype.skill = 'run';

function Child(){
    Parent.call(this,'我是parent');
    this.say = 'Hi!';
};
Child.prototype = new Parent();
let child2 = new Child();
console.log(child2);         //  {name: "我是parent", colors: Array(3), say: "Hi!"}
console.log(child2.skill);   //  run
console.log(Child.prototype.constructor);   //  Parent
Child.prototype.constructor = Child;

组合继承总结

  • 优点
  1. 可以继承父类的属性和方法,也可以继承父类原型链上的属性和方法;
  2. 弥补了原型链继承中引用属性共享的问题
  3. 可传参可复用
  • 缺点
  1. 调用了两次父类构造函数
  2. 子类实例的同名属性会覆盖父类的同名属性,不容易维护
  3. 子类实例的构造函数意外指向了父类的构造函数,需要重新维护
寄生组合继承
// 原型链继承
function Parent(name){
    this.name = name;
    this.colors = ['red','orange','green'];
};
Parent.prototype.skill = 'run';

function Child(){
    Parent.call(this,'我是parent');
    this.say = 'Hi!';
};
//  和组合继承相比 只有这个地方有改动
//  等同于
Child.prototype = Object.create(Parent.prototype);
let child3 = new Child();
console.log(child3);
console.log(child3.skill); 
console.log(Child.prototype.constructor);

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

Child.prototype = Object.create(Parent.prototype);

等同于

Child.prototype.__proto__ = Parent.prototype;

寄生组合继承相较与组合继承来说改进了一点,少调用了一次父类的构造函数,其它没什么不同的;

寄生组合继承上边我们说是接近完美的继承,这种方式的缺点是什么呢?来看这样一个例子

function Car(color){
    this.music = function(){
        console.log('lalala');
    }
    this.skill = function(){
        console.log('run');
    }
    this.addOil = function(){
        console.log('100#');
    }
}

Car这个父类构造函数,子类继承的话,是会继承到addOil这个方法,问题是,有的子类并不需要这个方法,比如新能源车,它是不需要加油的;

继承的最大问题在于:无法决定继承哪些属性,所有属性都得继承。

那怎样解决这类问题呢,用组合,就是先设计好零件,需要就加上

function music(){
    console.log('lalala');
}
function skill(){
    console.log('run');
}
function addOil(){
    console.log('100#');
}
let car = compose(music,skill,addOil);
let newEnergyCar = compose(music,skill);
ES6 Class继承

和ES6 Class继承相关的就是constructor super

class Parent{
    constructor(name){
        this.name = name;
        this.logFunc = function(){
            console.log(this.name);
        }
    }
    say = 'Hi!'
    skill(params) {
        console.log('running~~');
    }
}
class Child extends Parent{
    constructor(name){
        super(name);
        this.sex = 'boy';
    }
    eat = 'apple';
}
let child1 = new Child('child1');

//  {say: "Hi!", name: "child1", eat: "apple", sex: "boy", logFunc: ƒ}
console.log(child1);  

如果不写 constructor,子类继承的时候会默认加上 constructor,还可以这样写

function Parent(name){
    this.name = name;
    this.colors = ['red','orange','green'];
};
class Child extends Parent{
    constructor(name){
        super(name);
        this.sex = 'boy';
    }
    eat = 'apple';
}
let child1 = new Child('child1');
//  {name: "child1", colors: Array(3), eat: "apple", sex: "boy"}
console.log(child1);  

super 是必写的,还是上边的代码,我们不写super,看会出现什么

function Parent(name){
    this.name = name;
    this.colors = ['red','orange','green'];
};
class Child extends Parent{
    constructor(){
        this.sex = 'boy';
    }
    eat = 'apple';
}
let child1 = new Child();
/*
    Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
*/ 

关于报错我是这样理解的,suer的作用是创造父类的实例对象this,然后再用子类的构造函数去改变它。

因为ES6 class 继承采用的方法也是寄生组合继承,说明了这种方法的成功;

参考文章: