# 继承

# 构造函数继承

经典继承:通过call()来实现继承 (相应的, 你也可以用apply)。

function Parent(name){
    this.name = name;
}
Parent.prototype.age = 330;
Parent.prototype.login = function () {
    console.log('用户名字' + this.name)
}
function Child(name){
    Parent.call(this, name);
    this.type = 'child';
}
const p1 = new Child('Libai')

p1.name;        // "Libai"
p1.age;         // undefind
p1.login();     // p1.login is not a function

当然,如果继承真地如此简单,那么本文就没有存在的必要了,本继承方法也存在明显的缺陷—— 构造函数式继承并没有继承父类原型上的方法

# 原型链继承

function Parent(){
    this.name = 'Libai';
    this.play = [1, 2, 3]
}
Parent.prototype.getName = function () {
    console.log(this.name);
}
function Child(){

}
Child.prototype = new Parent()
Child.prototype.constructor = Child;
const p1 = new Child()
p1.getName()        // 'Libai'

看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:

const p1 = new Child();
p1.play.push(4);
console.log(p1.play);   // [1, 2, 3, 4]
const p2 = new Child();
console.log(p2.play);   // [1, 2, 3, 4]
  • 明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象,引用类型的属性被所有实例共享
  • 在创建 Child 的实例时,不能向Parent传参

# 组合继承

原型链继承和经典继承双剑合璧。

组合式继承是 JS 最常用的继承模式,但组合继承使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
    console.log(this.name)
}
function Child (name, age) {
    Parent.call(this, name);    // 第一次
    this.age = age; 
}

Child.prototype = new Parent();     // 第二次
Child.prototype.constructor = Child;

const child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

const child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

# 寄生组合继承

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
    console.log(this.name)
}
function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child1 = new Child('kevin', '18');

console.log(child1);

这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了在Parent.prototype上面创建不必要的、多余的属性。

与此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf

开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

# ES6 extends

Class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class Parent {
    constructor(name) {
	    this.name = name;
    }
    doSomething() {
	    console.log('parent do something!');
    }
    sayName() {
        console.log('parent name:', this.name);
    }
}

class Child extends Parent {
    constructor(name, parentName) {
        super(parentName);
        this.name = name;
    }
    sayName() {
        console.log('child name:', this.name);
    }
}

const child = new Child('son', 'father');
child.sayName();            // child name: son
child.doSomething();        // parent do something!

const parent = new Parent('father');
parent.sayName();           // parent name: father