分享

对象冒充的多重继承

 yiyiyicz 2012-11-09
function ClassA(color){
    this.color = color;
    this.sayColor = function(){
        console.log(this.color);   
    }
}
function ClassB(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name)
    }
}

//通过对ClassA、ClassB使用CALL方法,改变其THIS的指向,让其this指向被实例化的C这个新对象,从而让C的实例能够创建出A和B的方法和属性
function ClassC(price,scolor,sname){
    ClassA.call(this,scolor);  //实现继承
    ClassB.call(this,sname);  //实现继承
   this.price = price;
    this.price = function(){
        console.log(this.price);   
    }
}
var oC = new ClassC(1200,"red","anyCall");
oC.price();
oC.sayColor();
oC.sayName();


通过对ClassA、ClassB使用CALL方法,改变其THIS的指向,让其this指向被实例化的C这个新对象,从而让C的实例能够创建出A和B的方法和属性(APPLY方法和CALL方法,只是后面传的参数有区别)

 

与23楼第一段代码类似,但是增加了处理多级继承的方法

希望达到的效果:

  1. function A(){
  2.     alert('a');
  3. }
  4. function B(){
  5.     this.$supClass();
  6.     alert('b');
  7. }
  8. extend(B,A);
  9. function C(){
  10.     this.$supClass();
  11.     alert('c');
  12. }
  13. extend(C,B);
  14. var c = new C();
  15. alert( c instanceof A ); //true
  16. alert( c instanceof B ); //true
  17. alert( c instanceof C ); //true
复制代码

实例:

  1.        function extend(subClass,supClass){
  2.            var fun = function(){},
  3.                prototype = subClass.prototype;
  4.            fun.prototype = supClass.prototype;
  5.            subClass.prototype = new fun();
  6.            for(var i in prototype){
  7.                subClass.prototype[i] = prototype[i];
  8.            }
  9.            subClass.$supClass = supClass;
  10.            subClass.prototype.$supClass = function(){
  11.                var supClass = arguments.callee.caller.$supClass;
  12.                if(typeof supClass == 'function'){
  13.                     supClass.apply(this,arguments);
  14.                     this.$supClass = supClass;
  15.                }
  16.            };
  17.            subClass.prototype.constructor = subClass;
  18.            return subClass;
  19.        }
  20. function A(){
  21.        alert('a');
  22. }
  23. function B(){
  24.        this.$supClass();
  25.        alert('b');
  26. }
  27. extend(B,A);
  28. function C(){
  29.        this.$supClass();
  30.        alert('c');
  31. }
  32. extend(C,B);
  33. var c = new C();
  34. alert( c instanceof A ); //true
  35. alert( c instanceof B ); //true
  36. alert( c instanceof C ); //true
复制代码

extend是这样写的:

  1. function extend(subClass,supClass){
  2.      var fun = function(){},
  3.          prototype = subClass.prototype;
  4.      fun.prototype = supClass.prototype;
  5.      subClass.prototype = new fun();
  6.      for(var i in prototype){
  7.          subClass.prototype[i] = prototype[i];
  8.      }
  9.      subClass.$supClass = supClass;
  10.      subClass.prototype.$supClass = function(){
  11.          var supClass = arguments.callee.caller.$supClass;
  12.          if(typeof supClass == 'function'){
  13.               supClass.apply(this,arguments);
  14.               this.$supClass = supClass;
  15.          }
  16.      };
  17.      subClass.prototype.constructor = subClass;
  18.      return subClass;
  19. }
复制代码

为什么不这样写:

  1. function extend(subClass,supClass){
  2.     var fun = function(){},
  3.         prototype = subClass.prototype;
  4.     fun.prototype = supClass.prototype;
  5.     subClass.prototype = new fun();
  6.     for(var i in prototype){
  7.         subClass.prototype[i] = prototype[i];
  8.     }
  9.     subClass.prototype.$supClass = function(){
  10.         supClass.apply(this,arguments);
  11.     };
  12.     subClass.prototype.constructor = subClass;
  13.     return subClass;
  14. }
复制代码

在只有一级继承时它会运行的很好,但是,如果多级继承,就会造成死循环,因为:

  1. subClass.prototype.$supClass = function(){
  2.     supClass.apply(this,arguments);
  3. };
复制代码

这个方法会被一直覆盖重写掉,而造成死循环。
而我的做法是,用类的$supClass属性指向它所继承的父类构造,在prototype中也有个$supClass方法,这个$supClass第一次必须要在类的构造器中执行,prototype.$supClass在执行时,会通过arguments.callee.caller.$supClass来获得类的$supClass,然后通过apply在this执行。 这样$subClass就能根据不同的来,来获得类的父类构造器并执行。

JavaScript继承
http://jiangzhengjun./blog/468758

所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。尽管ECMAScript并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。

创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类中的属性和方法。

JavaScript中的继承机制并不是明确规定的,而是通过模仿实现的。



  • 对象冒充

构造原始的ECMAScript时,根本没打算设计对象冒充(object masquerading)。它是在开发者开始理解函数的工作方式,尤其是如何在函数环境中使用this关键字后才发展出来的。
其原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使ClassA的构造函数成为ClassB的方法,然后调用它。ClassB就会收到ClassA的构造函数中定义的属性和方法。
所有的新属性和新方法都必须在删除父类构造函数的代码行后定义,否则,可能会覆盖子类的相关属性和方法。
多层继承代码示例如下:
Js代码

  1. 1.function classA(sColor) {   
  2. 2.    this.color = sColor;   
  3. 3.    this.sayColor = function () {   
  4. 4.        alert(this.color);   
  5. 5.    };   
  6. 6.}   
  7. 7.  
  8. 8.//classB继承classA   
  9. 9.function classB(sColor, sName) {   
  10. 10.    this.newMethod = classA;   
  11. 11.    this.newMethod(sColor);   
  12. 12.    delete this.newMethod;   
  13. 13.      
  14. 14.    //以下是自己的属性与方法,一般在调用父类构造后定义   
  15. 15.    this.name = sName;   
  16. 16.    this.sayName = function () {   
  17. 17.        alert(this.name);   
  18. 18.    };   
  19. 19.}   
  20. 20.//classC继承classB   
  21. 21.function classC(sColor, sName, iNum) {   
  22. 22.    this.newMethod = classB;   
  23. 23.    this.newMethod(sColor, sName);   
  24. 24.    delete this.newMethod;   
  25. 25.      
  26. 26.    //以下是自己的属性与方法,一般在调用父类构造后定义   
  27. 27.    this.iNum = iNum;   
  28. 28.    this.sayNum = function () {   
  29. 29.        alert(this.iNum);   
  30. 30.    };   
  31. 31.}   
  32. 32.var objC = new classC("blue", "name", 10);   
  33. 33.objC.sayColor();//blue   
  34. 34.objC.sayName();//name   
  35. 35.objC.sayNum();//10  
复制代码

对象冒充可以支持多重继承 :Java代码

  1. 1.function classZ() {   
  2. 2.    this.newMethod = classX;   
  3. 3.    this.newMethod();   
  4. 4.    delete this.newMethod;   
  5. 5.  
  6. 6.    this.newMethod = classY;   
  7. 7.    this.newMethod();   
  8. 8.    delete this.newMethod;   
  9. 9.               //添加新方法   
  10. 10.          //...   
  11. 11.}  
复制代码

这里存在一个弊端,如果ClassX和ClassY具有同名的属性或方法,ClassY具有高优先级,因为继承的是最后的类。除这点小问题之外,用对象冒充实现多继承机制轻而易举。由于这种继承方法的流行,ECMAScript的第三版为Function对象加入了两个新方法,即call()和 apply()。



  • call()方法

call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作this的对象。其他参数都直接传递给函数自身。例如:
Php代码

  1. 1.function sayColor(sPrefix, sSuffix) {   
  2. 2.    alert(sPrefix + this.color + sSuffix);   
  3. 3.}   
  4. 4.var obj = new Object();   
  5. 5.obj.color = "red";   
  6. 6.//The color is red, a very nice color indeed.   
  7. 7.sayColor.call(obj, "The color is ", ", a very nice color indeed.");  
复制代码

在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this 。对象 obj 的 color 属性等于 "red " 。调用 call() 方法时,第一个参数是 obj ,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj 。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 prefix 和 suffix 匹配。

要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:
Js代码

  1. 1.function classB(sColor, sName) {   
  2. 2.    //this.newMethod = classA;   
  3. 3.    //this.newMethod(sColor);   
  4. 4.    //delete this.newMethod;   
  5. 5.    ClassA.call(this, sColor);   
  6. 6.    this.name = sName;   
  7. 7.    this.sayName = function () {   
  8. 8.        alert(this.name);   
  9. 9.    };   
  10. 10.}
复制代码


  •   apply()方法

apply()方法有两个参数,用作this的对象和要传递给函数的参数的数组。例如:
Js代码

  1. 1.function sayColor(sPrefix, sSuffix) {   
  2. 2.    alert(sPrefix + this.color + sSuffix);   
  3. 3.}   
  4. 4.var obj = new Object();   
  5. 5.obj.color = "red";   
  6. 6.//The color is red, a very nice color indeed.   
  7. 7.sayColor.apply(obj, new Array("The color is ", ", a very nice color indeed."));  
复制代码

这个例子与前面的例子相同,只是现在调用的是 apply() 方法。调用 apply() 方法时,第一个参数仍是 obj ,说明应该赋予 sayColor() 中的 this 关键字值是 obj 。第二个参数是由两个字符串构成的数组,与 sayColor() 的参数 prefix 和 suffix 匹配。

该方法也用于替换前三行的赋值、调用和删除新方法的代码:
Js代码

  1. 1.function classB(sColor, sName) {   
  2. 2.    //this.newMethod = classA;   
  3. 3.    //this.newMethod(sColor);   
  4. 4.    //delete this.newMethod;   
  5. 5.    ClassA.apply(this, new Array(sColor));   
  6. 6.    this.name = sName;   
  7. 7.    this.sayName = function () {   
  8. 8.        alert(this.name);   
  9. 9.    };   
  10. 10.}  
复制代码


  • 原型链

继承这种形式在ECMAScript中原本是用于原型链的。prototype对象的任何属性和方法都被传递给那个类的所有实例 。 原型链利用这种功能来实现继承机制。如果用原型方式重定义前面例子中的类,它们将变为下列形式:

Js代码

  1. 1.function ClassA() {   
  2. 2.}   
  3. 3.ClassA.prototype.color = "red";   
  4. 4.ClassA.prototype.sayColor = function () {   
  5. 5.    alert(this.color);   
  6. 6.};   
  7. 7.function classB() {   
  8. 8.}   
  9. 9.ClassB.prototype = new ClassA();  
复制代码

注意,调用 ClassA 的构造函数时,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。


子类的所有属性和方法都必须出现在prototype属性被赋值后,因为在它之前赋值的所有方法都会被删除 。为什么?因为prototype属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为ClassB类添加name属性和sayName()方法的代码如下:
Js代码

  1. 1.function ClassA() {   
  2. 2.}   
  3. 3.ClassA.prototype.color = "red";   
  4. 4.ClassA.prototype.sayColor = function () {   
  5. 5.    alert(this.color);   
  6. 6.};   
  7. 7.function ClassB() {   
  8. 8.}   
  9. 9.ClassB.prototype = new ClassA();   
  10. 10.//下面的属性与方法赋值一定要在原型属性赋值后,   
  11. 11.//prototype默认为object   
  12. 12.ClassB.prototype.name = "";   
  13. 13.ClassB.prototype.sayName = function () {   
  14. 14.    alert(this.name);   
  15. 15.};   
  16. 16.var objA = new ClassA();   
  17. 17.var objB = new ClassB();   
  18. 18.objA.color = "red";   
  19. 19.objB.color = "blue";   
  20. 20.objB.name = "name";   
  21. 21.objA.sayColor();//red   
  22. 22.objB.sayColor();//blue   
  23. 23.objB.sayName();//name  
复制代码

此外,在原型链中, instanceof 运算符的运行方式也很独特。对 ClassB 的所有实例, instanceof 为 ClassA 和 ClassB 都返回 true 。例如:

Php代码

  1. 1.var objB = new ClassB();   
  2. 2.alert(objB instanceof ClassA);//true   
  3. 3.alert(objB instanceof ClassB);//true
复制代码

在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。

原型链的弊端是不支持多重继承 。记住,原型链会关键是用另一类型的对象重写类的原有默认 prototype 属性 。


 

原型链持多层继承

虽然原型链不支持多重继承,但支持多层继承,请看:

Js代码

  1. 1.function Base1() {   
  2. 2.}   
  3. 3.Base1.prototype.base1Name = "base1Name";   
  4. 4.Base1.prototype.getBase1 = function () {   
  5. 5.    alert(this.base1Name);   
  6. 6.};   
  7. 7.//Base2继承Base1   
  8. 8.function Base2() {   
  9. 9.}   
  10. 10.Base2.prototype = new Base1();   
  11. 11.Base2.prototype.base2Name = "base2Name";   
  12. 12.Base2.prototype.getBase2 = function () {   
  13. 13.    alert(this.base2Name);   
  14. 14.};   
  15. 15.//Base3继承Base2   
  16. 16.function Base3() {   
  17. 17.}   
  18. 18.Base3.prototype = new Base2();   
  19. 19.Base3.prototype.base3Name = "base3Name";   
  20. 20.Base3.prototype.getBase3 = function () {   
  21. 21.    alert(this.base3Name);   
  22. 22.};   
  23. 23.var base3 = new Base3();   
  24. 24.//Base3可以访问从父类Base2继承过来的方法   
  25. 25.base3.getBase2();//base2Name   
  26. 26.//Base3可以访问从超类Base1继承过来的方法   
  27. 27.base3.getBase1();//base1Name   
  28. 28.//当然更可以访问自己的方法   
  29. 29.base3.getBase3();//base3Name   
  30. 30.//因为原型链方法,所以支持instanceof   
  31. 31.alert(base3 instanceof Base1);//true   
  32. 32.alert(base3 instanceof Base2);//true   
  33. 33.alert(base3 instanceof Base3);//true   
复制代码


  • 混合方式

这种继承方式使用构造函数定义类,并未使用任何原型。对象冒充的主要问题是必须使用构造函数方式。 这不是最好的选择。不过如果使用原型链,就无法使用带参构造函数了 。开发者该如何选择呢?答案很简单,两者都用。
创建类的最好方式是用构造函数方式定义属性,用原型方式定义方法 。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法 。用这两种方式重写前面的例子,代码如下:
Js代码

  1. 1.function ClassA(sColor) {   
  2. 2.    this.color = sColor;   
  3. 3.}   
  4. 4.ClassA.prototype.sayColor = function () {   
  5. 5.    alert(this.color);   
  6. 6.};   
  7. 7.function ClassB(sColor, sName) {   
  8. 8.    ClassA.call(this, sColor);//①   
  9. 9.    this.name = sName;   
  10. 10.}   
  11. 11.ClassB.prototype = new ClassA();//②   
  12. 12.ClassB.prototype.sayName = function () {   
  13. 13.    alert(this.name);   
  14. 14.};  
复制代码

在此例子中,继承机制由①②两行的代码实现。在第一行显示的代码中,在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。在第二行的代码中,用原型链继承 ClassA 类的方法。由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。

下面的例子测试了这段代码:

Js代码

  1. 1.var objA = new ClassA("red");   
  2. 2.var objB = new ClassB("blue", "name");   
  3. 3.objA.sayColor();//red   
  4. 4.objB.sayColor();//blue   
  5. 5.objB.sayName();//name
复制代码


  • 不能采用动态原型方法实现继承

继承机制不能采用动态化的原因是,prototype对象的独特本性。看下面代码:
Js代码

  1. 1.function ClassA(name) {   
  2. 2.    this.name = name;   
  3. 3.    if (typeof ClassA._initialized == "undefined") {   
  4. 4.        ClassA.prototype.get = function () {   
  5. 5.            alert(name);   
  6. 6.        };   
  7. 7.        ClassA._initialized = true;   
  8. 8.    }   
  9. 9.}   
  10. 10.function ClassB(name, age) {   
  11. 11.    ClassA.call(this, name);   
  12. 12.    this.age = age;   
  13. 13.    if (typeof ClassB._initialized == "undefined") {   
  14. 14.        //不能在ClassB里改变他的prototype属性,即使改变后   
  15. 15.        //也只是下一新对象起作用   
  16. 16.        ClassB.prototype = new ClassA();   
  17. 17.        ClassB.prototype.get = function () {   
  18. 18.            alert(name + " " + age);   
  19. 19.        };   
  20. 20.        ClassB._initialized = true;   
  21. 21.    }   
  22. 22.}   
  23. 23.var o1 = new ClassB("name", 1);   
  24. 24.//第一次创建的对象时,prototype未生效,以下方法找不着   
  25. 25.//o1.get();   
  26. 26.o1 = new ClassB("name", 1);   
  27. 27.//第二次创建的对象时,prototype才生效   
  28. 28.o1.get();//name 1  
复制代码

上面的代码展示了用动态原型定义ClassA与ClassB,
错误在于设置ClassB.prototype属性的代码。从逻辑上讲,这个位置是正确的,但从功能上讲,却是无效的。
从技术上说来,在代码运行前,对象已被实例化,并与原始的prototype对象联系在一起了。虽然用晚绑定可用在其他非 prototype属性值,但替换prototype对象却不会对该对象产生任何影响。只从第二个对象实例才会反映出这种改变,这就使上面实例变得不正确。
而正确的作法是把ClassB.prototype = new ClassA();提出来放在函数的外面即可,这样从创建第一个对象开始就能生效。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多