ES5面向对象js实现继承的最优方式

es5时代js面向对象的的编程需要重点关注的问题就是“继承”,要实现继承,在js中有多种方式,虽然各种方式都有利弊,没有做好的但是相较之下总有最优的,本篇博文将这几种方法逐一列举总结,参考来源《javascript高级程序设计》。

先准备一个父类:

    // 定义一个person类
    function Person (name) {
    // 属性
    this.name = name || 'Mr';
    // 实例方法
    this.sleep = function(){
        console.log(this.name + '正在睡觉!');
    }
    }
    // 原型方法
    Person.prototype.work = function(work) {
        console.log(this.name + '的工作是:' + work);
    };

1.原型链继承

将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,直到Object,最终形成原型链。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。关于显式原型prototype与隐式原型的关系:

1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。

2、所有的引用类型都有一个’_ _ proto_ _'属性(也叫隐式原型,它是一个普通的对象)。

3、所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。

4、所有引用类型,它的’_ _ proto_ _'属性指向它的构造函数的’prototype’属性。

5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。

代码实现:

 /**
     * 
     * 1.原型链实现继承
     * 将父类的实例作为子类的原型
     * 
     **/ 

    function Stu(){ }
    Stu.prototype = new Person();
    Stu.prototype.name = '张明';

    // Test Code
    var stu = new Stu();
    console.log(stu.name);
    stu.work('学生');
    stu.sleep();
    console.log(stu instanceof Person); 
    console.log(stu instanceof Stu); 

观察原型链:
在这里插入图片描述
特点:简单,易于实现

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到

缺点:

  1. 无法实现多继承
  2. 来自原型对象的引用属性是所有实例共享的,一处修改全部更改。
  3. 创建子类实例时,无法向父类构造函数传参

2.构造函数继承
使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类(没用到原型)

function Teacher(name){
        Person.call(this);
        this.name = name || '李老师';
    }

    // Test Code
    var teacher = new Teacher("王刚");
    console.log(teacher.name);
    teacher.work('老师');
    teacher.sleep();
    console.log(teacher instanceof Person); // false
    console.log(teacher instanceof Teacher); // true

输出:

在这里插入图片描述

特点:

  1. 子类实例可以共享父类引用属性
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  2. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3.实例继承

/**
     * 
     * 3.实例继承
     *给父类实例添加新特性,作为子类实例返回
    */

    function Engineer(name){
        var engineerObj = new Person()
        engineerObj.name = name || '刘工';
        return engineerObj;
    }

    var engineer = new Engineer();
    // Test Code
    engineer.work('工程师');
    engineer.sleep();
    console.log(teacher instanceof Person); // false
    console.log(teacher instanceof Teacher); // true

结果:
在这里插入图片描述
特点:
不限制调用方式,不管是new 子类()还是子类直接调用(),返回的对象具有相同的效果。

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

4.拷贝继承

 /**
     * 
     *  4.拷贝继承
     *将父类原型上的属性方法拷贝给子类
    */
    function Lawyer(name){
        var per = new Person();
        for(var p in per){
            Lawyer.prototype[p] = per[p];
        }
        Lawyer.prototype.name = name || 'Tom';
    }

    // Test Code
    var lawyer = new Lawyer("章三");
    lawyer.work('工程师');
    lawyer.sleep();
    console.log(lawyer instanceof Person); // false
    console.log(lawyer instanceof Teacher); // true

结果:
在这里插入图片描述
特点:
支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法

5.组合继承

 /**
     * 
     *  5.组合继承
     *1,2的组合
     *通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
    */

    function Doctor(name){
        Person.call(this);
        this.name = name || 'Tom';
    }
    Doctor.prototype = new Person();

    // 注意更改构造函数指向

    Doctor.prototype.constructor = Doctor;

    // Test Code
    var doctor = new Doctor("王主任");
    doctor.work('医生');
    doctor.sleep();
    console.log(doctor instanceof Person); // true
    console.log(doctor instanceof Doctor); // true

结果:
在这里插入图片描述
特点:

  1. 结合了1和2优点,可以继承实例属性/方法,也可以继承原型属性/方法
    既是子类的实例,也是父类的实例
  2. 不存在引用属性共享问题
  3. 可传参
  4. 函数可复用

缺点:
调用了两次父类构造函数,生成了两份实例

6.寄生组合继承

 /**
     * 
     *  6.寄生组合继承
     *5的优化
     *通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
    */

    function Programmer(name){
        Person.call(this);
        this.name = name || 'Tom';
    }

    (function(){
        // 创建一个没有实例方法的类
        var Super = function(){};
        Super.prototype = Person.prototype;
        //将实例作为子类的原型
        Programmer.prototype = new Super();
    })();
    Programmer.prototype.constructor = Programmer; // 需要修复下构造函数

    // Test Code
    var programmer = new Programmer("gcc");
    programmer.work('程序员');
    programmer.sleep();
    console.log(programmer instanceof Person); // true
    console.log(programmer instanceof Programmer); // true

结果:
在这里插入图片描述

注意:如果没有Programmer.prototype.constructor = Programmer;
在这里插入图片描述
构造方法指向父类

如果加上Programmer.prototype.constructor = Programmer;

在这里插入图片描述

综上寄生组合式继承才是最有选择。

发布了69 篇原创文章 · 获赞 6 · 访问量 1883

猜你喜欢

转载自blog.csdn.net/weixin_40073115/article/details/103768735