• 面向对象实现继承的几种方法


    1、原型链

    1.1、构造函数和原型和实例之间的关系

    每个构造函数都有一个原型对象,原型对象又有一个指向构造函数的指针,实例又有一个指向原型对象的指针。

    1.2、原型链的概念

    假如我们让原型对象等于另一个类型的实例,类型我理解为不同的构造函数,此时的原型对象将包含另一个原型对象的指针,相应的,另一个原型中也包含一个指向另一个构造函数的指针,假如,另一个原型是又另外一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

    function SuperType(){
        this.property=true;
    }
    SuperType.prototype.getSuperValue=function(){
        console.log(this.property);
    }
    function SubType(){
        this.subProperty=false;
    }
    SubType.prototype=new SuperType();   //继承了SuperType
    SubType.prototype.constructor=SubType;
    SubType.prototype.getSubValue=function(){
            console.log(this.subProperty);
    }
    var o = new SubType();
    o.getSuperValue();   //true

    以上代码定义了2个类型,SuperType和SubType,每个类型分别有一个类型和一个方法,他们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例并将该实例赋给SubType的原型实现的。实现的本质是重写原型对象,代之以一个新类型的实例,原来存在于SuperType的实例的属性和方法,现在也存在于SubType.prototype中了。

    注意:加背景那一行在继承了SuperType类型之后,立马更正了SubType的原型的constructor属性,因为,此时constructor属性已经指向了SuperType。

    问题:原型链虽然很强大,可以实现继承,但面对引用类型值的原型,有一个问题。

    function SuperType(){
        this.colors=["red","blue","green"];
    }
    function SubType(){
    
    }
    SubType.prototype=new SuperType();
    var o1=new SubType();
    o1.colors.push("pink");
    console.log(o1.colors);   //["red", "blue", "green", "pink"]
    var o2=new SubType();
    console.log(o2.colors);   //["red", "blue", "green", "pink"]

    以上代码,superType构造函数定义了一个colors属性,该属性包含一个数组(引用类型值),SuperType的每个实例都会有各自包含数组的colors数组。当SubType 通过原型链继承了SuperType 之后,SubType.prototype 就变成了SuperType 的一个实例,因此它也拥有了一个它自己的colors 属性,就跟专门创建了一个SubType.prototype.colors 属性一样。但结果是什么呢?结果是SubType 的所有实例都会共享这一个colors 属性。

    2、借用构造函数----伪造对象----经典继承

    由于原型中包含引用类型的值有问题,又有一种新的方法出来了,叫做借用构造函数,或者伪造对象,或者经典继承,在子类型构造函数的内部调用超类型构造函数。

    function SuperType(){
        this.colors=["red","green"];
    }
    SuperType.prototype.sayHi=function(){
        alert("hello~");
    }
    function SubType(){
        SuperType.call(this);
    }
    var o=new SubType();
    o.colors.push("pink");
    console.log(o.colors);    //["red", "green", "pink"]
    var o1=new SubType();  
    console.log(o1.colors);   //["red", "green"]
    o1.sayHi();         //报错

    以上代码中加背景的那一行调用了超类型的构造函数,通过使用call方法,我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数,这样的话,SubType的每个实例都会具有自己的colors属性的副本了。

    2.1、借用构造函数的优势-----传递参数

    相对于原型链来说,借用构造函数有一个优势就是,可以传递参数,即可以在子类型构造函数中向超类型构造函数传递参数。

    function SuperType(name){
        this.name=name;
    }
    function SubType(){
        SuperType.call(this,"张三");
        this.age=24;
    }
    var o=new SubType();
    alert(o.name);   //张三
    alert(o.age);   //24

    2.2、借用构造函数的问题

    如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题----方法都在构造函数中定义,因此函数的复用也就无从谈起了,而且在超类型的原型中定义的方法,对子类型而言也是不可见的。

    3、组合继承----伪经典继承----较为常用的继承方法

    组合继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式,思路是使用原型链实现原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样的话,即通过在原型上定义方法实现了函数的复用,又能保证每个实例都有他自己的属性。

    function SuperType(name){
        this.name=name;
        this.colors=["red","green"];
    }
    SuperType.prototype.sayName=function(){
        console.log(this.name);
    }
    function SubType(name,age){
        SuperType.call(this,name);  //继承了属性
        this.age=age;
    }
    SubType.prototype=new SuperType();  //继承了方法
    SubType.prototype.constructor=SubType;   //将SubType的原型的constructor属性指回SubType
    SubType.prototype.sayAge=function(){
        console.log(this.age);
    }
    
    var o=new SubType("张三",23);
    o.colors.push("pink");
    console.log(o.colors);  //["red", "green", "pink"]
    o.sayName();  //张三
    o.sayAge();   //23
    
    var o1=new SubType("李四",25);
    console.log(o1.colors);  //["red", "green"]
    o1.sayName();  //李四
    o1.sayAge();   //25

    以上代码中SuperType定义了2个属性name和colors,SuperType的原型定义了一个方法sayName(),SunType构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了它自己的属性age,然后,将SuperType的实例赋值给SubType的原型,然后,又在该新原型上定义了方法sayAge,这样一来,就可以让俩个不同的SubType实例既分别拥有自己的属性,包括colors属性,又可以使用相同的方法了,组合继承是javascript中最常用的继承模式。instanceof 和isPrototypeOf()也能够用于识别基于组合继承创建的对象。

    4、原型式继承

    这种方法不使用构造函数实现继承,由json格式的发明人Douglas Crockford,提出了一个object()函数。

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

    以上代码,在object函数的内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

    function object(o){
        function F(){};
        F.prototype=o;
        return new F();
    }
    var person={
        name:"张三",
        colors:["red","green"]
    }
    var o1=object(person);
    o1.name="李四";
    o1.colors.push("pink");
    console.log(o1.colors);     //["red", "green", "pink"]
            
    var o2=object(person);
    console.log(o2.colors);     //["red", "green", "pink"]

    这种原型式继承,要求你必须有一个对象作为另一个对象的基础,如果有那么一个对象的话,可以把它传递给object函数,然后再根据具体需求对得到的对象加以修改即可。

    ECMAScript 5,通过新增Object.create()方法规范化了原型式继承,这个方法接收2个参数,一个用作新对象原型的对象,和(可选的)一个为新对象定义额外属性的对象,在传入一个参数的情况下,Object.create()和上面的object()方法的行为相同。

    var Chinese={
        nation:"中国"
    }
    var Doctor={
        job:"医生"
    }
    var o=Object.create(Chinese);
    o.job="医生";
    
    console.log(o.nation);    //中国

    Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。例如:

    var Chinese={
        nation:"中国"
    }
    var Doctor={
        job:"医生"
    }
    var o=Object.create(Chinese,{
        job:{
            value:"医生"
        }
    });
    
    console.log(o.job);    //医生

    支持Object.create()方法的浏览器有IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome。

    只想让一个对象和另一个对象保持类似的情况下,原型式继承是可以的。

    5、寄生式继承

    寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真的是它做了所有工作一样返回对象。

    function Object(o){
        function F(){}
        F.prototype=o;
        return new F();
    }
    function createAnother(original){
        var clone=Object(original);
        clone.sayHi=function(){    //以某种方式来增强这个对象
            console.log("hi~");
        }
        return clone;
    }
    
    var person={
        name:"张三",
        colors:["red","green"]
    }
    
    var anotherPerson=createAnother(person);
    anotherPerson.sayHi();     //hi~

    以上例子中的代码,基于person返回了一个新对象,anotherPerson,新对象不仅具有person的所有属性和方法,还有属于自己的sayHi方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。

    6、继承组合式继承----最为理想的继承方法

    组合继承是javascript最常用的继承模式,但是,它最大的问题就是无论什么情况下,都会调用2次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,虽然子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性,回顾一下组合继承。

    function SuperType(name){
        this.name=name;
        this.colors=["red","green"];
    }
    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.sayAge=function(){
        console.log(this.age);
    }
    var o1=new SubType("张三",23);
    o1.colors.push("pink");
    console.log(o1.colors);    //["red", "green", "pink"]
    
    var o2=new SubType("李四",25);  
    console.log(o2.colors);   //["red", "green"]

    以上代码,第一次调用SuperType构造函数时,SubType.prototype会得到俩个属性,name和colors,它们都是SuperType的实例属性,只不过现在位于SubType的原型中,当调用SubType的构造函数时,又会调用一次SuperType构造函数,这一次,又在新对象上创建了实例属性,name和colors,于是,这俩个实例属性就屏蔽了原型中的俩个同名属性。

    寄生组合式继承的基本模式如下:

    function inheritPrototype(SubType,SuperType){
        var prototype=Object(SuperType.prototype);  //创建对象
        prototype.constructor=SubType;    //增强对象
        SubType.prototype=prototype;    //指定对象
    }

    以上自定义函数中,接收2个参数,子类型构造函数和超类型构造函数,在函数内部第一步,创建超类型原型的一个副本,第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性,最后一步,将新创建的对象(即副本)赋值给子类型的原型,以下,将这个函数运用于前面的例子:

    function Object(o){
        function F(){}
        F.prototype=o;
        return new F();
    }
    function inheritPrototype(SubType,SuperType){
        var prototype=Object(SuperType.prototype);  //创建对象
        prototype.constructor=SubType;    //增强对象
        SubType.prototype=prototype;    //指定对象
    }
    
    function SuperType(name){
        this.name=name;
        this.colors=["red","green"];
    }
    SuperType.prototype.sayName=function(){
        console.log(this.name);
    }
    function SubType(name,age){
        SuperType.call(this,name);
        this.age=age;
    }
    inheritPrototype(SubType,SuperType);
    SubType.prototype.sayAge=function(){
        console.log(this.age);
    }
    
    var o1=new SubType("张三",23);
    o1.colors.push("pink");
    console.log(o1.colors);   //["red", "green", "pink"]
            
    var o2=new SubType("李四",25);
    console.log(o2.colors);    //["red", "green"]

    以上代码,高效率体现在它只调用了一次SuperType构造函数,并且,因此避免了在SubType.prototype上创建不必要的,多余的属性,现在SubType.prototype上面只有sayName方法和sayAge方法了,寄生组合式继承是引用类型最为理想的继承方式。

  • 相关阅读:
    Codeforces Round #365 (Div. 2) D
    Codeforces Round #414 C. Naming Company
    Codeforces Round #365 (Div. 2) B
    LA 6893 The Big Painting(矩阵Hash)
    Gym100783C Golf Bot(FFT)
    POJ 2481 Cows(树状数组)
    POJ 2352 Stars
    POJ 2299 Ultra-QuickSort(树状数组+离散化)
    LightOJ 1341 Aladdin and the Flying Carpet(唯一分解定理)
    LightOJ 1356 Prime Independence(质因数分解+最大独立集+Hopcroft-Carp)
  • 原文地址:https://www.cnblogs.com/xlj-code/p/8342318.html
Copyright © 2020-2023  润新知