• 读书笔记之JavaScript对象继承 - [Development]

    2007-11-22

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://fallenlord.blogbus.com/logs/10912579.html

    还是《Professional JavaScript for Web Developers》。
    JavaScript也可以对象继承?当我看到这一章第一个反应便是这个,以前从来没有想过的,呵呵。
    JS实现继承有如下几种办法:

    1. 对象冒充

    function ClassA(sColor) {
      this.color = sColor;
      this.sayColor = function() {
        alert(this.color);
      };
    }
    function ClassB(sColor, sName) {
      this.newMethod = ClassA;
      this.newMethod(sColor);
      delete this.newMethod;
      this.name = sName;
      this.sayName = function() {
        alert(this.name);
      };

    }
    var objA = new ClassA("red");
    var objB = new ClassB("blue", "Nicholas");
    objA.sayColor();  //outputs "red"
    objB.sayColor();  //outputs "blue"
    objB.sayName();  //outputs "Nicholas"

    呵呵,是不是很有趣。注意黄色代码,所有新的属性和新的方法必须在删除了newMethod的代码行后定义。否则,可能会覆盖超类的相关属性和方法。
    然后。。。用这个方法,还可以实现多重继承,哈哈哈

    function ClassZ() {
      this.newMethod = ClassX;
      this.newMethod();
      delete this.newMethod;
      this.newMethod = ClassY;
      this.newMethod();
      delete this.newMethod;
    }

    不过这里有个小弊端,就是如果ClassX和ClassY有同名的属性和方法的话,ClassY具有优先级,使用时要注意点,呵呵。

    2. call()方法

    先看看call()方法的使用:

    function sayColor(sPrefix, sSuffix) {
      alert(sPrefix + this.color + sSuffix);
    };
    var obj = new Object();
    obj.color = "red";
    sayColor.call(obj, "The color is ", ", a very nice color indeed.");

    这个例子中,sayColor虽然在对象外定义,即使他不属于任何对象,也可以引用关键字this。调用call()方法时,第一个参数是obj,说明应该赋予sayColor()函数中的参数的this关键字值是obj,第二个和第三个参数就是sayColor()函数本身的参数sPrefix和sSuffix
    要与冒充对象方法一起使用该方法,只需要将前三行的赋值、调用和删除代码替换即可:

    function ClassA(sColor) {
      this.color = sColor;
      this.sayColor = function() {
        alert(this.color);
      };
    }
    function ClassB(sColor, sName) {
      //this.newMethod = ClassA;
      //this.newMethod(sColor);
      //delete this.newMethod;
      ClassA.call(this, sColor);

      this.name = sName;
      this.sayName = function() {
        alert(this.name);
      };
    }
    var objA = new ClassA("red");
    var objB = new ClassB("blue", "Nicholas");
    objA.sayColor();  //outputs "red"
    objB.sayColor();  //outputs "blue"
    objB.sayName();  //outputs "Nicholas"

    3. apply()方法

    apply()方法和call()方法很相似,唯一不同的就是将call()方法后面带的多个参数存入数组再进行传递:

    function sayColor(sPrefix, sSuffix) {
      alert(sPrefix + this.color + sSuffix);
    };
    var obj = new Object();
    obj.color = "red";
    sayColor.apply(obj, new Array("The color is ", ", a very nice color indeed."));

    自然,很容易得出用apply()方法实现继承的代码:

    function ClassA(sColor) {
      this.color = sColor;
      this.sayColor = function() {
        alert(this.color);
      };
    }
    function ClassB(sColor, sName) {
      //this.newMethod = ClassA;
      //this.newMethod(sColor);
      //delete this.newMethod;
      ClassA.apply(this, new Array(sColor));

      this.name = sName;
      this.sayName = function() {
        alert(this.name);
      };
    }
    var objA = new ClassA("red");
    var objB = new ClassB("blue", "Nicholas");
    objA.sayColor();  //outputs "red"
    objB.sayColor();  //outputs "blue"
    objB.sayName();  //outputs "Nicholas"

    其中,如果超类参数顺序与子类相同,图中黄色区域可以这么写:

    ClassA.apply(this, arguments);

    4. 原型链

    function ClassA() {
    }
    ClassA.prototype.color = "red";
    ClassA.prototype.sayColor = function() {
      alert(this.color);
    };
    function ClassB() {
    }
    ClassB.prototype = new ClassA();
    ClassB.prototype.name = "Nichloas";
    ClassB.prototype.sayName = function() {
      alert(this.name);
    }


    这个。。黄色那一句还是比较神奇的,呵呵。
    不过要注意,调用ClassA的构造函数时,没有给它传递参数,这在原型链中是标准做法,要确保构造函数没有任何参数!
    同时要注意上面的代码,和对象冒充类似,子类的所有的属性和方法都必须出现在prototype属性被赋值后,因为在它之前赋值的所有方法都会被删除!
    原型链的好处在于可以使用类似如下代码检测:

    var objB = new ClassB();
    alert(objB instanceof ClassA)  //outputs "true"
    alert(objB instance of ClassB) //outputs "true"

    而它的坏处在于不能多重继承,不过用惯Java的人应该比较习惯吧,呵呵。

    5. 混合方式

    总结以上几种方式,对象冒充的问题是必须使用构造函数方式,这不是最好的选择(参考我的另一片读书笔记《读书笔记之JavaScript的类编写方法》)。但如果使用原型链,又无法使用带参数的构造函数了。那么混合模式就是解决这两个问题的最好答案了,呵呵:

    function ClassA(sColor) {
      this.color = sColor;
    }
    ClassA.prototype.sayColor = function() {
      alert(this.color);
    };
    function ClassB(sColor, sName) {
      ClassA.call(this, sColor);
      this.name = sName;
    }
    ClassB.prototype = new ClassA();
    ClassB.prototype.sayName = function() {
      alert(this.name);
    };

    这种方式是推荐使用的,呵呵。

    最后还是那句话,以上源代码均来自Nicholas C. Zakas的《Professional JavaScript for Web Developers》


    收藏到:Del.icio.us