• 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),是为了给父类传递参数

    参考

  • 相关阅读:
    Java反射之访问私有属性或方法
    java字符串中显示双引号
    什么导致spring事务失效
    ActiveMq性能优化
    JFrame关闭事件处理
    c3p0数据库连接池死锁问题
    Mongodb性能优化
    Spring事务配置的五种方式
    ActiveMq启动停止
    JScrollPane动态加载图片
  • 原文地址:https://www.cnblogs.com/bonly-ge/p/12089014.html
Copyright © 2020-2023  润新知