JavaScript高级程序设计【面向对象-继承】

187 阅读6分钟

1.原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

可以通过下列方法构造原型链:

    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

可以通过两种方式来确定原型和实例之间的关系: instanceof

alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true 或者是isProtoyepeOf alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true 原型链的问题:

    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"

之前也提到过,引用类型的话会被共享,恩可以这样说吧?

原型链的第二个问题是:在创建子类型的实例是,不能像超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上前面刚刚讨论股偶的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

2.借用构造函数

constructor stealing的技术,有时候也叫作伪造对象或者经典继承。这种技术的基本思想是在子类型构造函数内部调用超类型构造函数。如:

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"

代码中SuperType.call(this)这行代码借调了超类型的构造函数。通过使用call()方法,或者apply()方法也可以,我们实际上是在(未来将要)新创建的SubType实例环境下调用了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

constructor stealing的问题在于,无法避免构造函数模式存在的问题:方法都在构造函数中定义,因此函数复用就无从谈起了。而且在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3.组合继承

combination inheritance,有时候也叫伪经典继承,指的是将原型链和借用构造函数的技术结合到一起,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
    
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };

    function SubType(name, age){  
        SuperType.call(this, name);
        
        this.age = age;
    }

    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

4.原型式继承 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到这个目的,他给出了如下函数

function object(o){ function F(){}

F.prototype=o;

return new F();

}

在object函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后反悔了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。

    function object(o){
        function F(){}
        F.prototype = o;
        return new F();
    }
    
    var person = {
        name: "Nicholas",
        friends: ["Shelby", "Court", "Van"]
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
    
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    
    alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础,如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可。 ECMAScript5通过新增Object.create()方法规范花了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()方法和object()方法的行为相同。 var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };

    var anotherPerson = Object.create(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
    
    var yetAnotherPerson = Object.create(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    
    alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

5.寄生式继承 寄生式(parasitic)继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是他做了所有工作一样返回对象。一下代码示范了寄生式继承模式。

function createAnother(original){ var clone=object(original);

clone.sayHi=function(){ alert("hi");

}

return clone;

} 在这个例子中,createAnother()函数接收了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给object()函数,将返回的结果赋值给clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。可以像下面这样来使用createAnother()函数:

var person={ name:"asd",

friends:["q","w","e"]

};

var anotherPerson=createAnother(person);

anotherPerson.sayHi();

6.寄生组合式继承 寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非是超类型原型的一个副本而已。本质上们就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式的基本模式如下:

function inheritPrototype(subType,superType){ var prototype=object(superType.prototype);//创建对象

prototype.constructor=subType;//增强对象

subType.prototype=prototype;//指定对象

}

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

    function inheritPrototype(subType, superType){
        var prototype = object(superType.prototype);   //create object
        prototype.constructor = subType;               //augment object
        subType.prototype = prototype;                 //assign object
    }
                            
    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
    
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };

    function SubType(name, age){  
        SuperType.call(this, name);
        
        this.age = age;
    }

    inheritPrototype(SubType, 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

这个例子的高效率性在于他只调用了一次SuperType构造函数,并且因此避免了在SubType.protorype上面创建不必要的、多余的属性。榆次同时,原型链还能保持不变。 YUI的yahoo.lang.extend()方法采用了寄生组合继承。