zoukankan      html  css  js  c++  java
  • 面向对象的程序设计-3-继承

    写在前面

      都知道,当我们读取一个对象的属性或方法的时候,会优先在这个对象上面找,如果在这个对象上找不到就会遍历他的原型,还没找到?--->原型的原型,又没找到?-->继续往上。。。

      这便是原型链的功用。下面,我探讨了一下原型链的使用与扩展,依靠原型链实现继承。

      至于什么是继承? 我的理解是,一个对象可以直接使用另一个对象的属性和方法。

      本文结构:

      1. 直接使用原型链
      2. 借用构造函数
      3. 组合继承
      4. 原型式继承
      5. 寄生式继承
      6. 寄生组合式继承

      其中的继承方式层层递进,不断进化完善缺点。

      进化过程:  1 → 2 → 3↘

                    → → 6

               4 → 5 ↗

    一、直接使用原型链

      1.回顾我的上一遍文章中写的,构造函数、原型、实例之间的关系::

        每一个构造函数都有prototype指针指向一个原型对象,原型对象有一个constructor指针指向构造函数,而实例通过[[prototype]]指针共享一个原型对象。

      2.原型链继承的实现:把一个构造函数的原型对象等于另一种类型(另一种构造函数)的实例

    function SuperType() {
        this.property = 'aaa';
    }
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    }
    
    function SubType() {
        this.subproperty = 'bbb';
    }
    SubType.prototype = new SuperType();
    
    SubType.prototype.getSubValue = function() {
        return this.subproperty;
    }
    var instance = new SubType();
    console.log(instance)

      关系图解

       执行的结果:SubType.prototype 指向了一个 SuperType 的实例。并通过 SubType.prototype.getSubValue 为实例添加了方法。

      当最终实例 instance 调用 getSuperValue 方法的时候。 先遍历实例instance对象,没找到--->搜索instance.prototype(也是SuperType的一个实例),还没找到--->搜索instnce.prototype.prototype (也是SuperType.prototype);

      3.注意

        3.1 别忘记默认的原型

          原型链可以无限长(当然为了性能,不要搞太长)。但是去到最后必然是 Object.prototype

        3.2 确定原型与实例的关系:原型链中的出现的原型都算该实例的原型

           instance instanceOf SubType // true   

             SuperType.isPrototypeOf(instance) // true 

          3.3 谨慎地定义方法: 因为是修改了SubType的原型对象,因此必须在替换之后才定义原型上的方法: SubType.prototype.getSubTypeValue() 并且不能使用字面量,不然又换了一个新对象。

      4.直接使用原型链继承的优缺点

         SuperType构造函数中若使用了引用类型值。就会放映在SubType的原型上。在原型上出现引用类型,容易被实例修改。影响所有的实例。

         使用 SubType 创建对象的时候,不能再给SuperType传递参数了。因为SubType.prototype 已经是一个SuperType的实例了。

     二、借用构造函数实现继承

      为解决直接使用原型链继承中不能给 SuperType 传递参数的问题以及引用类型值问题而生。

    function SuperType(name) {
        this.colors = ['red','blue','green'];
        this.name = name;
    }
    
    function SubType(name) {
        SuperType.call(this, name);
    }
    
    var instance1 = new SubType('jody');
    var instance2 = new SubType('miaowwwww');
    instance1.colors.push('black');
    console.log(instance1)
    console.log(instance2);
    console.log(instance2.name)

       首先要知道,构造函数只不过是在特定的环境(this)中执行代码,并为它定义属型而已。因此,大可以在SubType中执行以下SuperType,这样一来,SubType 就获取 SuperType 的属性和方法。

       优点:解决了原型链继承的引用类型问题,以及 传递参数的问题

       缺点:若仅仅使用借用构造函数,无法避免构造函数模式的问题,(方法都在实例中,方法的复用性降低了),并且instance1 并不是SuperType 的一个实例,无法使用instanceOf,isPrototypeOf.

    三、组合继承

      鉴于借用构造函数继承的缺点,当然不会仅仅借用构造函数啦。

      组合继承又称伪经典继承,结合了原型链和借用构造函数 两种技术。(相信,大家看完借用构造函数的缺点就已经想到了)

    function SuperType(name) {
        this.colors = ['red','blue','green'];
        this.name = name;
    }
    
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    }
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    
    // 继承方法、
    SubType.prototype = new SuperType();    // 为了使用SuperType.prototype上的方法,所以要添加
    SubType.prototype.constructor = SubType; //new 的时候要调用这个constructor?并没有,这个属性只是标记这个对象是这个类型,多一个判断方法而已。这里仅仅是补全替换prototype导致的constructor的缺失
    SubType.prototype.sayAge = function() {    // 在SubType原型上,同时也是SuperType的实例
        console.log(this.age);
    }
    
    var instance1 = new SubType('jody', 22);
    console.log(instance1)

        这种方式确实完美解决了传递参数,引用类型,公共方法重用的缺点。同时也可以被 instanceOf 和 isPrototypeOf() 识别。

        但是它产生了新的问题:SubType实例含有(colors,name)属性,SubType.prototype也有(colors,name)这部分属性冗余了。(这个问题将在 六、组合式继承中解决)

        

     四、原型式继承

    // 原型式继承即通过object函数实现
    function object(o) {
        function F() {};
        F.prototype = o;
        return new F();
    }
    
    // 实例
    var person = {
        name: 'miaowwwww',
        friends: ['aaa', 'bbb']
    };
    
    var person1 = object(person);
    person1.name = 'jody';
    person1.friends.push('ccc');
    console.log(person1)

       原型式继承跟原型链是一个理念(把原型指向另一种类型的对象(实例))。把空构造函数的原型指向一个对象,然后创建空构造函数的实例,并放回。

      4.1 object函数在es5 中得到了实现:Object.create(obj, defineProperties) :第二个参数而可选,设置属性,及其描述,描述默认false

    var person2 = Object.create(person, {
        name:{
            value: 'dd',
            writable: false
        },
        age: {
            value: 22
        }
    })
    console.log(person2)

      4.2 缺点:跟原型链一样,是原型上的引用类型值

    五、寄生式继承

      5.1 寄生式是在原型式的基础上的改进。   —_— 只是把定义实例属性的逻辑放到一个函数里面了而已

    function craateAnother(original) {
        var clone = Object.create(original);
        clone.sayName = function() {
            console.log(this.name);
        };
        return clone;
    }
    var person = { name: 'jody', friends: ['aa','bb']};
    var person1 = createAnother(person);

       5.2 缺点:公共的属性和方法(如:sayName)不能复用。

    六、寄生组合式继承

       这是 三、组合继承 和 五、寄生式继承的技术组合。(组合继承使用寄生式继承修改了,组合继承的缺点。

      1.先贴出组合继承的代码理解一下:

        组合继承的缺点:两次调用SuperType(),  导致的 SubType.prototype产生了冗余的(name,colors...)等SuperType的属性。

    function SuperType(name) {
        this.name = name;
        this.colors = ['aa','bb'];
    }
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    }
    function SubType(name, age) {
        SuperType.call(this, name);        //    第二次执行SuperType
        this.age = age;
    }
    SubType.prototype = new SuperType();    //第一次执行SuperType
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function() {
        console.log(this.age);
    }

       既然知道 SubType.prototype = new SuperType(); 只是为了获取SuperType.prototype。 那么可以绕过执行SuperType,直接获取SuperType.prototype。(这便是寄生式的作用)

      2 寄生式继承的用处 : 使用Object.create() 创建一个空的实例,并且该实例的prototype 指向了 SuperType.prototype. (干净,无冗余属性)

    function inheritPrototype(subType, superType) {
        var emptyInstance = Object.create(superType.prototype);
        emptyInstance.constructor = subType; //补充constructor的缺失
        subType.prototype = emptyInstance;
    }

      3.寄生组合式继承代码

    function inheritPrototype(subType, superType) {
        var prototype = Object.create(superType.prototype);
        prototype.constructor = subType; //补充constructor的缺失
        subType.prototype = prototype;
    }
    function SuperType(name) { this.name = name; this.colors = ['aa','bb']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); // SubType.prototype = SuperType.prototype; //不可以这么做,因为sayAge会添加在SuperType.prototype中 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var instance = new SubType('jody', 22); console.log(instance) console.log(instance.name)

       至此: 寄生组合式继承被认为是引用类型最理想的继承范式。

     备注:(接受指导与批评)

       本文是阅读高级程序设计(第三版)P162~P174 的理解与总结。

  • 相关阅读:
    CodeForces
    CodeForces
    CodeForces 718C && HDU 3572 && Constellation
    CodeForces
    USACO 5.4 tour的dp解法
    10.22~10.28一周经典题目整理(meeting,BZOJ4377,POJ3659)
    codeforces 724D
    codeforces 724C
    hdu5909 Tree Cutting
    hdu5822 color
  • 原文地址:https://www.cnblogs.com/miaowwwww/p/6257657.html
Copyright © 2011-2022 走看看