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

    一般的面向对象程序语言,有两种继承方法——接口继承(interface inheritance)和实现继承(implementation inheritance)。接口继承只继承方法签名,而实现继承则继承实际的方法。在JavaScript中,函数没有签名,所以在JavaScript只支持实现继承,而且主要是依靠原型链(prototype chaining)来是实现的。

    原型链(prototype chaining)

    利用原型来继承属性和方法。回顾一下构造函数(constructor),原型对象(prototype)和实例(instance)的关系。每一个构造函数都有一个prototype属性,该属性指向一个prototype对象;prototype对象也有constructor属性,指向该函数;而实例也有一个内部指针(__proto__)指向这个prototype对象。如果这个prototype对象是另外一个对象的实例会是怎样的呢?这样该prototype对象就包含一个指向另一个类型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。

    JS的继承很简单,就是把子类的prototype设为父类的一个(实例化)对象

    function SuperType(){
        this.property = true;
    }
                       
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
                       
    function SubType(){
        this.subproperty = false;
    }
                       
    //inherit from SuperType
    SubType.prototype = new SuperType();
                       
    SubType.prototype.getSubValue = function (){
        return this.subproperty;
    };
                       
    var instance = new SubType();
    alert(instance.getSuperValue());   //true 

    最终的结果:instance的__proto__指向SubType.prototype对象,而SubType.prototype对象的__proto__属性又指向SuperType.prototype对象。getSuperValue()是一个方法,所以仍然存在于原型中,而property是一个实例属性,所以现在存在于SubType.prototype这个实例中。  instance.constructor现在指向的是SuperType,这是由于SubType.prototype指向SuperType.prototype,而SuperType.prototype的constructor属性指向SuperType函数,所以instance.constructor指向SuperType。

    默认情况下,所有引用类型都继承Object。这是因为所有函数的原型对象,默认都是Object的一个实例,所以内部prototype(__proto__)指向Object.Prototype。

    原型和实例的关系:可以使用2种方法来确定原型与实例之间的关系。
    - instancef操作符:使用该操作符来测试实例与原型链中出现过的构造函数,都会返回true

    alert(instance instanceof Object); //true
    alert(instance instanceof SuperType); //true
    alert(instance instanceof SubType); //true

    - isPrototypeOf()方法:只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。

    alert(Object.prototype.isPrototypeOf(instance)); //true
    alert(SuperType.prototype.isPrototypeOf(instance)); //true
    alert(SubType.prototype.isPrototypeOf(instance)); //true

     

    给子类添加方法的注意点:我们有的时候会给子类添加方法,或者是重写父类的某些方法。这个时候就要注意,这些方法必须在继承后再定义。以下的例子里,SubType在继承SuperType后,我们给它添加了新的方法getSubValue(),而且重写了getSuperValue()方法。对于后者,只有SubType的实例才会使用重写的方法,SuperType的实例还是会使用原有的getSuperValue()方法。

    function SuperType(){
      this.property = true;
    }
    SuperType.prototype.getSuperValue = function(){
      return this.property;
    };
    function SubType(){
      t his.subproperty = false;
    }
    //inherit from SuperType
    SubType.prototype = new SuperType();
    //new method
    SubType.prototype.getSubValue = function (){
      return this.subproperty;
    };
    //override existing method
    SubType.prototype.getSuperValue = function (){
      return false;
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //false

    另外一个需要注意的是,通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。如下面的代码,在SubType继承SuperType以后,使用对象字面量给原型添加方法,但这样做,会重写SubType原型,重写后的SubType.prototype包含的是一个Object的实例,从而也切断了与SuperType的关系。

    function SuperType(){
      this.property = true;
    }
    SuperType.prototype.getSuperValue = function(){
      return this.property;
    };
    function SubType(){
      this.subproperty = false;
    }
    //inherit from SuperType
    SubType.prototype = new SuperType();
    //try to add new methods - this nullifies the previous line
    SubType.prototype = {
      getSubValue : function (){
        return this.subproperty;
      },
      someOtherMethod : function (){
        return false;
      }
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //error!

    原型链的问题:和原型一样,当使用引用类型值的时候,原型链就会出问题了。回顾一下之前的内容,包含一个引用类型值的原型属性会被所有实例共享,这就是为什么我们要把引用类型值在构造函数中定义,而不是在原型中定义。在通过原型链实现继承时,原型实际上会变成另一个类型的实例,于是,原先的实例属性也顺利成章的变成现在的原型属性了。

    function SuperType(){
      this.colors = [“red”, “blue”, “green”];
    }
    function SubType(){
    }
    //inherit from SuperType
    SubType.prototype = new SuperType();
    var instance1 = new SubType();
    instance1.colors.push(“black”);
    alert(instance1.colors); //”red,blue,green,black”
    var instance2 = new SubType();
    alert(instance2.colors); //”red,blue,green,black”

    在SuperType构造函数中,我们定义了一个colors数组,每一个SuperType实例都会拥有它自己的这个colors数组。但是当SubType使用原型链继承SuperType以后,SubType.prototype变成SuperType的一个实例,因此它拥有自己的colors属性,也就是说SubType.prototype.colors属性。所以,当创建SubType实例的时候,所有实例都共享这一属性了。如上面的代码所示。

    第二个问题就是:在创建子类的实例时,不能向超类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。由于这些问题,我们不会单独使用原型链。


    借用构造函数(Contructor stealing)
    为了解决上述问题,开发人员发明了一种叫做借用构造函数的技术。这种技术的思路就是:在子类型构造函数的内部调用超类型构造函数。(函数,只不过是在特定环境中执行代码的对象?)我们可以通过使用apply()或call()方法在新创建的对象上执行构造函数。

    function SuperType(){
      this.colors = [“red”, “blue”, “green”];
    }
    function SubType(){
      //inherit from SuperType
      SuperType.call(this);
    }
    var instance1 = new SubType();
    instance1.colors.push(“black”);
    alert(instance1.colors); //”red,blue,green,black”
    var instance2 = new SubType();
    alert(instance2.colors); //”red,blue,green”

    我们在SubType里使用call()方法调用SuperType的构造函数,实际上就是在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果就是每个SubType实例都具有自己的colors属性的副本。

    传递参数:使用借用构造函数方法的一个很大的好处在于就是,我们可以从子类的构造函数传递参数到父类的构造函数中。

    function SuperType(name){
      this.name = name;
    }
    function SubType(){
      //inherit from SuperType passing in an argument
      SuperType.call(this, “Nicholas”);
      //instance property
      this.age = 29;
    }
    var instance = new SubType();
    alert(instance.name); //”Nicholas”;
    alert(instance.age); //29

    新的SuperType构造函数新增了一个参数name,我们在call SuperType的同时,往SuperType传递参数"Nicholas"。为了不让超类型的构造函数重写子类型的属性,可以在调用超类型构造函数后再定义子类的属性。

    借用构造函数的问题:方法都在构造函数中定义,无法复用。而且在超类型的原型中定义的方法,对子类型而言是不可见的。结果所有类型都只能使用构造函数模式。


    组合继承
    结合原型链及借用构造函数各自的优点的一种继承模式。使用原型链继承原型属性及方法,使用借用构造函数来继承实例属性。如下面例子,我们使用call()方法调用SuperType的构造函数(每个SubType实例都拥有自己的name和colors属性,以及SubType的age属性);然后再把SuperType实例赋值给SubType的原型,使其继承SuperType的sayName()方法(每个实例都共用这个方法)。

    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
    
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    function SubType(name, age){
        //inherit properties
        SuperType.call(this, name);
        this.age = age;
    }
    //inherit methods
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function(){
        alert(this.age);
    };
    
    var instance1 = new SubType("Nicholas", 29);
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    instance1.sayName(); //"Nicholas";
    instance1.sayAge(); //29
    var instance2 = new SubType("Greg", 27);
    alert(instance2.colors); //"red,blue,green"
    instance2.sayName(); //"Greg";
    instance2.sayAge(); //27

    原型式继承(Prototypal Inheritance)

    function object(o){
      function F(){}
      F.prototype = o;
      return new F();
    }

    寄生式继承(Parasitic Inheritance)

    缺点同构造函数

  • 相关阅读:
    sigpending
    js 动态计算折扣后总价格
    让ie6支持fixed最简单和实用的方法
    jquery提示气泡
    在线API,桌面版,jquery,css,Android中文开发文档,JScript,SQL掌用实例
    三元组表
    B-树
    二叉排序树
    顺序查找
    二分查找
  • 原文地址:https://www.cnblogs.com/rexmzk/p/2640492.html
Copyright © 2011-2022 走看看