07-继承

作者: LeoCong | 来源:发表于2017-05-22 15:24 被阅读0次

继承

Javascript中继承都基于两种方式:
1.通过原型链继承,通过修改子类原型的指向,使得子类实例通过原型链查找,实现继承父类的属性和方法。这种方式可以让所有实例共享原型链上的方法和属性。但当某个实例修改引用类型的属性时,其他实例也会受影响(引用地址相同)。
2.借用构造函数,通过apply、call方法在子类中调用父类构造函数,子类实例继承了父类构造函数的实例属性和方法。这个方式继承的属性和方法相互独立,但实例方法重复创建,没有共享。

  • 原型链继承
    原理:将父类的实例对象作为子类的原型对象。
    原型链继承的实质是重写子类的原型对象,将父类的实例作为子类的新原型,于是子类新原型就继承了父类所有的属性和方法。
    function Parent() {}; // 父类
    function Child() {}; // 子类
    var c1 = new Child(); // 继承前子类的实例
    Child.prototype = new Parent();//将子类的原型重写为父类的实例
    var p1 = new Child(); // 继承后子类的实例
    // 没有修改Child.constructor时
    console.log(c1.__proto__.constructor.prototype === p1.__proto__); // true
    console.log(p1.__proto__.__proto__.constructor === Parent); // true
原型链继承.png

将父类的实例对象作为子类的原型对象,也就是将原来存在父类实例中的属性和方法,继承给了子类的原型对象。
当读取某个子类实例对象的属性时:1.首先在子类实例中查找;2.如果没找到,就到子类的原型(也就是父类的实例)中查找;3.如果没找到,就在父类的原型中查找;4.如果没找到,就到Object.prototype中查找。

    function Parent () {
        // 父类的实例属性
        this.name = 'Parent'; //基本类型的实例属性,实例之间相互独立
        this.age = [30, 35];// 引用类型的实例属性,所有实例共享
    }
    // 父类的原型方法
    Parent.prototype.getName = function () {
        console.log(this.name);
    }
    function Child () {}
    // 将父类的实例对象作为子类的原型对象
    Child.prototype = new Parent();
    // 子类原型的继承了父类实例的所有属性和方法
    var p1 = new Child();
    var p2 = new Child();
    p1.getName(); // Parent, 子类继承了父类的原型方法
    console.log(p1.age); // [30, 35],子类继承了父类的实例属性
    // 修改子类实例的属性
    p1.name = 'Tom'; // 修改基本类型的属性
    p1.age.push(40); // 修改引用类型的属性
    // 基本类型属性值相互独立
    p1.getName(); // Tom
    p2.getName(); // Parent
    // 引用类型的属性被所有实例共享,修改某一实例的值会影响其他实例
    console.log(p1.age); // [30, 35, 40]
    console.log(p2.age); // [30, 35, 40]

子类覆盖父类中某个方法或添加父类中不存在的某个方法时,需要写在替换原型语句的后面。
并且不能使用字面量(相当于Object的实例)的方法创建子类原型方法,否则会覆盖之前的原型。
原型链继承的缺点:
1.使用原型链继承时,父类的实例成为子类的原型,父类的实例属性就变成了原型属性。而引用类型值的原型属性会被所有实例共享。
2.创建子类实例时,不能向父类构造函数传递参数。

  • 借用构造函数继承
    原理:在子类构造函数中,调用父类构造函数。(没有用到原型)
    使用call()或apply()方法可以在创建子类实例时,调用执行父类构造函数,这样子类实例就会执行父类构造函数定义的初始化代码。
    创建子类实例时,可以向父类传递参数。并且子类实例不会共享父类引用类型属性,因为继承的都是父类的实例属性和方法,不在原型链上。
    function Parent(name) {
        this.name = name;
        this.age = [30, 35];
    }
    function Child(name) {
        // 调用父类的构造函数
        // Parent.call(this, name);
        Parent.apply(this, arguments);
    }
    var p1 = new Child('Leo');
    var p2 = new Child('Tom');
    console.log(p1.name); // Leo
    console.log(p1.age); // [30, 35]
    p1.age.push(40);
    console.log(p1.age); // [30, 35, 40]
    console.log(p2.age); // [30, 35]不会共享父类引用类型属性

借用构造函数继承的缺点:
1.只能继承父类的实例属性和方法,不能继承原型属性和方法(没有使用原型)。
2.方法都在定义在构造函数中,每次创建实例都会创建一遍方法,无法实现函数复用,影响性能。

  • 组合继承
    原理:通过原型链实现原型属性和方法的继承,通过借用构造函数来实现实例属性和方法的继承。
    一般在构造函数上定义实例属性,在原型上定义方法,通过组合继承既满足方法的复用,又保证实例有自己的属性。
    function Parent(name) {
        this.name = name;
        this.colors = ['blue', 'red'];
    }
    Parent.prototype.getName = function () {
        console.log(this.name);
    }
    function Child(name, age) {
        // 调用父类的构造函数
        Parent.call(this, name); // 第二调用Parent()
        this.age = age;
    }
    // 原型链继承
    Child.prototype = new Parent();// 第一调用Parent()
    Child.prototype.constructor = Child;
    var p1 = new Child('Leo', 26);
    var p2 = new Child('Tom', 20)
    p1.colors.push('green');
    p1.getName(); // Leo
    console.log(p1.age); // 26
    console.log(p1.colors); // ["blue", "red", "green"]
    console.log(p2.colors); // ["blue", "red"]

组合继承的缺点:
调用两次父类构造函数。第一次是在重写子类原型的时,将子类的原型指向父类实例,此时子类原型会继承父类所有的属性和方法(包括实例属性和方法及原型属性和方法)。第二次是在子类调用构造函数创建实例时,在子类构造函数中调用父类构造函数,此时新创建的子类实例对象的实例属性和方法(直接继承来自父类构造函数的实例属性和方法)会覆盖子类原型中的同名实例属性和方法。

  • 原型式继承
    原理:使用Object.create()方法,将传入的对象作为空构造函数的原型,并返回这个空构造函数的实例。
    这种方法不需要创建构造函数,只需传入一个对象,就能从传入的对象衍生出新的对象。本质上还是通过原型链,实例的原型指向传入的对象。
    Object.create()方法接受一个对象为参数,必须传一个对象否则报错,返回一个新的对象,传入的对象作为新对象的原型。
    该方法还可接受第二个参数,该参数是属性描述对象,它所描述的对象属性,会添加到新对象。
    Object.create()方法生成的对象,继承了它原型对象的构造函数。
    // 模拟Object.create()
    function create(obj) {
        function F() {}
        F.prototype = obj;
        return new F();
    }

使用Object.create()实现继承。

    function Parent(name) {
        this.name = name;
        this.colors = ['blue', 'red'];
        this.getName = function () {
            console.log(this.name);
        }
    }
    Parent.prototype.getColor = function () {
        console.log(this.colors);
    }
    var p1 = new Parent('Leo');
// 使用Object.create()方法,需传入一个对象
    var c1 = Object.create(p1);
    var c2 = Object.create(p1);
    c1.colors.push('green');
    c1.getName();// Leo
    c1.getColor(); // ["blue", "red", "green"]
    c2.getColor(); // ["blue", "red", "green"]

原型式继承缺点:
传入的对象(原型)的引用类型属性,会被所有创建出来的实例共享。

  • 寄生式继承
    原理:创建一个仅用于封装继承过程的函数,在该函数内部创建一个新对象,再增强对象,最后返回这个新对象。
    寄生继承的思路跟工厂模式差不多,就是调用一个仅用于封装继承过程的函数。
    寄生继承不用实例化父类,直接实例化一个临时副本实现了原型链继承。
    在该函数内部创建新对象的方法很多:可以使用Object.create(),new或者使用字面量创建。
    function createObj(obj) {
        // 创建新对象()
        var clone = Object.create(obj);
        // 增强这个对象
        clone.sayHi = function() {
            console.log('Hi,'+name);
        }
        // 返回这个对象
        return clone;
    }

寄生式继承缺点:
无法实现函数复用(封装在一个函数内,没有用到原型,和构造函数模式一样)。

  • 寄生组合式继承
    原理:组合继承的改进版,还是使用原型链来继承原型属性和方法,借用构造函数来继承实例属性和方法。但是,不将子类原型指向父类实例,而是创建一个空构造函数,将父类的原型指向这个空构造函数的原型,将子类的原型指向这个空构造函数的实例。从而实现子类原型间接继承父类原型。
   // 1.模拟Object.create()
    function object(o) {
       function F() {}; // 创建空构造函数
       F.prototype = o; // 将空构造函数的原型指向传入的对象
       return new F();  // 返回构造函数的实例对象,该实例对象继承了传入对象的属性和方法
    }
    // 封装继承函数
   function extend(Child, Parent) {
       // 传入父类的原型,返回的实例obj继承了父类的原型属性和方法
       var obj = object(Parent.prototype);
       // 子类的原型指向obj,子类的原型间接继承了父类的原型属性和方法
       Child.prototype = obj;
       // 修正子类新原型(即obj对象)的constructor,使其指向子类构造函数
       obj.constructor = Child;
   }

   // 2.封装成一个函数
   function extend(Child, Parent) {
       var F = function () {};
       // 空构造函数的原型指向父类的原型,相当于复制了一份父类原型
       F.prototype = Parent.prototype;
       // 子类的原型指向空构造函数的实例,相当于子类原型间接继承了父类原型
       Child.prototype = new F();
       // 修正子类原型constructor指向
       Child.prototype.constructor = Child;
   }

   // 3.直接使用Object.create()方法
   function extend(Child, Parent) {
       var obj = Object.create(Parent.prototype);
       Child.prototype = obj;
       obj.constructor = Child;
   }
寄生组合继承.png
    function Parent (name) {
        this.name = name;
        this.colors = ['blue', 'red'];
    }
    Parent.prototype.getName = function () {
        console.log(this.name);
    }
    function Child (name, age) {
      // 通过借用构造函数继承父类的实例属性和方法
      Parent.call(this, name);
      this.school = age; // 添加新的子类属性
    }
    // 声明继承函数
    function extend(Child, Parent) {
        var F = function () {};
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Child;
    }
    // 寄生式继承父类原型属性和方法
    extend(Child, Parent);
    var p1 = new Child('Leo', 26);
    var p2 = new Child('Tom', 20);
    p1.colors.push('green');
    p1.getName(); // Leo
    console.log(p1.colors); //["blue", "red", "green"]
    console.log(p2.colors); //["blue", "red"]

参考资料:
《JavaScript高级程序设计》
《JavaScript 标准参考教程》

相关文章

  • 07-继承

    继承 Javascript中继承都基于两种方式:1.通过原型链继承,通过修改子类原型的指向,使得子类实例通过原型链...

  • #07-事件响应#

    07-事件响应

  • 07-内存管理、命名空间和继承

    《C++文章汇总》[https://www.jianshu.com/p/bbd842438420]上一篇介绍了引用...

  • 在线流视频m3u8文件解析,AES-128

    代码地址:python-spider/07-开课吧9980 at master · appke/python-sp...

  • TensotFlow 应用实例:07-优化器 Optimizer

    TensotFlow 应用实例:07-优化器 Optimizer 介绍 本文是我在学习TensotFlow 的时候...

  • 使用docker部署artipub(含权限认证)

    使用docker部署artipub(2021/06/07-含权限认证) 1. 安装docker及docker-co...

  • -07-

    过了目的地,踏上返程。女孩心中无限的不舍,却又夹杂着对母亲的怀抱和家的温暖的想念,只得跟着父亲的脚步,恋恋不舍的离...

  • 2017-10-07

    早上好!#幸福实修#~每天进步1%#幸福实修12班-07-徐静--杭州# 20171007(12//60) 【幸福...

  • 2017-10-09

    早上好!#幸福实修#~每天进步1%#幸福实修12班-07-徐静--杭州# 20171009(14/60) 【幸福三...

  • 2017-10-11

    早上好!#幸福实修#~每天进步1%#幸福实修12班-07-徐静--杭州# 20171011(16/60) 【幸福三...

网友评论

      本文标题:07-继承

      本文链接:https://www.haomeiwen.com/subject/aszbxxtx.html