7种继承实现对比

什么是继承

可以实现一个类创建的实例拥有另一个类的属性和方法

1.原型链继承

思路:子类的原型指向父类实例,通过子类实例的原型链向上查找达到继承的作用

实现

// 父类
function Parent(age) {
    this.name = 'bonly';
    this.age = age;
    this.hobby = ['basketball','pingpong']
}

// 子类
function Child() {

}

let p = new Parent('18');
Child.prototype = p;

let child1 = new Child();
let child2 = new Child();

console.log(child1.name); // bonly
console.log(child2.name); // bonly

/**
 * 1. 所有子类实例的属性都指向构造函数的原型也就是父类实例
 * 
 */
console.log(child1.name === child2.name);  // true
/**
 * 2. 父类实例修改属性后,所有子类实例的属性都会被修改
 */
p.name = 'bonly1';
console.log(child1.name); // bonly1
console.log(child2.name); // bonly1

/**
 * 3. 引用类型的问题
 */
child1.hobby.push('skiing')
console.log(child1.hobby);
console.log(child2.hobby);
/**
 * 4. 我们无法为不同的实例初始化继承来的属性,可以看到所有实例的年龄都是18
 */
console.log(child1.age); // 18
console.log(child2.age); // 18

特点

  • 父类新增原型方法、原型属性,子类都能访问到
  • 简单,易于实现

问题

  • 无法实现多继承
  • 来自原型对象的所有属性被所有实例共享
  • 创建子类实例时,无法向父类构造函数中传递参数
  • 要想为子类新增属性和方法,必须要在Child.prototype = new Parent();之后执行,不能放到构造器中

2. 构造函数(经典继承)

思路:在子类构造函数的内部调用父类行构造函数

实现

// 父类
function Parent(age) {
    this.name = 'bonly';
    this.age = age;
    this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
    console.log(`我今年${this.age}`);
}
// 子类
function Child(...args) {
    Parent.call(this,...args)
}
/**
 * 只能实现部分继承,会继承父类的属性和方法,不能继承父类原型的属性和方法
 */
let child1 = new Child(18);
let child2 = new Child(20);
console.log(child1); //Child { name: 'bonly', age: 18, hobby: [ 'basketball', 'pingpong' ] }
console.log(child2); // Child { name: 'bonly', age: 20, hobby: [ 'basketball', 'pingpong' ] }

child1.hobby.push('skiing')
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'skiing' ]

console.log(child2.hobby); // [ 'basketball', 'pingpong' ]

特点

  • 可以实现多继承
  • 可以创建实例的时候给构造函数传递参数
  • 解决了原型链继承中子类实例共享父类引用属性的问题
  • 可以实现多继承

问题

  • 实例并不是父类的实例,只是子类的实例
  • 只能实现继承父类的构造函数的属性和方法,不能继承父类原型上的方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本(父类实例函数指的是什么?),影响性能

3. 组合继承(构造函数+原型链)

思路:子类调用父类构造,子类原型指向父类实例,然后修复子类构造函数指向

实现

// 父类
function Parent(age) {
    this.name = 'bonly';
    this.age = age;
    this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
    console.log(`我今年${this.age}`);
}
// 子类
function Child(...args) {
    Parent.call(this,...args)
}
Child.prototype = Object.create(Person.prototype,{
    constructor:{
        value:Child,
        enumerable:false,
        writable:true,
        configurable:true
    }
});
console.log(Child.prototype.constructor); // [Function: Parent]

Child.prototype.constructor = Child; // 修复构造函数原型的指向
console.log(Child.prototype.constructor); // [Function: Child]

let child1=  new Child(18);
let child2 = new Child(20);
child1.hobby.push('skiing');
console.log(child1); //Child {  name: 'bonly', age: 18, hobby: [ 'basketball', 'pingpong', 'skiing' ]}
console.log(child2); // Child { name: 'bonly', age: 20, hobby: [ 'basketball', 'pingpong' ] }
child1.say(); // 18
child2.say(); // 20

特点

  • 可以继承实例的属性/方法,也可以继承父类原型的方法
  • 可传递参数,不存在引用属性共享问题
  • 创建实例的时候可传递参数
  • 函数可以复用(哪个函数实现的复用,是父类原型的方法吗??)

4. 原型继承

思路:在creat方法的内部先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例

实现

// 也是object.create的实现原理
function create(obj) {
    function F() { }
    F.prototype = obj;
    return new F();
}

let Parent = {
    name: 'bonly',
    age: '18',
    hobby: ['basketball', 'pingpong'],
    say(){
        console.log(`我今年${this.age}`);
    }
}
let child1 = create(Parent);
let child2 = create(Parent);
child1.say();
child2.say();
console.log(child1.name);
console.log(child2.name);
child1.hobby.push('shiing')
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'shiing' ]
console.log(child2.hobby); // [ 'basketball', 'pingpong', 'shiing' ]

let child3 = Object.create(Parent);
console.log(child3.hobby===child1.hobby); // true
child3.say();

特点

  • 可以继承实例的属性和方法
  • 方法需要写到对象上,子类才能共享

问题

  • 无法通过传递参数来创建实例(创建两个不同年龄的子类实例)
  • 传入对象的属性有引用类型,所有实例都会共享相应的值

5. 寄生式继承

思路:在原型式的基础上,包装一个函数,用来给创建出的实例增加自身的方法

实现

// 创建对象
function create(obj) {
    function F() { }
    F.prototype = obj;
    return new F();
}

// 扩展对象
function extend(obj) { 
    let clone = create(obj);
    clone.hi = function(){
        console.log(`hi,我是${this.name}`);
    }
    return clone;
}

let Parent = {
    name: 'bonly',
    age: '18',
    hobby: ['basketball', 'pingpong'],
    say() {
        console.log(`我今年${this.age}`);
    }
}
let child1 = extend(Parent);
let child2 = extend(Parent);
child1.say();
child2.say();
child1.hi();
child2.hi();
let child3 = Object.create(Parent);
console.log(child3.hobby === child1.hobby); // true
child3.say();

问题

  • 在增强对象内为不同实例增加函数,函数不能复用

6. 寄生组合式继承

思路:在增强对象的时候把子类的构造函数指向子类

实现


// 父类
function Parent(age) {
    this.name = 'bonly';
    this.age = age;
    this.hobby = ['basketball','pingpong']
}
Parent.prototype.say = function(){
    console.log(`我今年${this.age}`);
}
// 子类
function Child(age) {
    Parent.call(this,age)
}
// 创建对象
function create(obj) {
    function F() { }
    F.prototype = obj;
    return new F();
}

// 扩展对象
function extend(subClass,superClass) { 
    let clone = create(superClass.prototype); // 创建对象
    // 增强对象,达到函数复用
    clone.constructor = subClass; // 增强对象
    subClass.prototype = clone; // 指定对象
}

extend(Child,Parent);
let child1 = new Child(18);
let child2 = new Child(20);
child1.say();// 我今年18
child2.say();// 我今年20
console.log(child1.name); // bonly
console.log(child2.name); // bonly

7. es6继承

原理:ES5是先创建子类的实例,然后在子类实例的基础上创建父类的属性。而ES6正好是相反的,是先创建父类的实例,然后在父类实例的基础上扩展子类属性。

实现

class Parent {
    constructor(name,age) {
        this.name = name;
        this.age = age;
        this.hobby = ['basketball','pingpong']
    }
    say(){
        console.log(`我${this.age}岁了`);
    }
}

class Child extends Parent{
    constructor(name,age){
        super(name,age);
    }
}

let child1 = new Child('bonly1',18);
child1.say(); // 我18岁了
child1.hobby.push('shiing');
console.log(child1.hobby); // [ 'basketball', 'pingpong', 'shiing' ]

let child2 = new Child('bonly2',20);
child2.say(); // 我20岁了
console.log(child2.hobby); // [ 'basketball', 'pingpong' ]

问题

  • 为什么一定要调用super方法

子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value),是为了给父类传递参数

参考

posted @ 2019-12-23 23:11  bonly-ge  阅读(324)  评论(0编辑  收藏  举报