zoukankan      html  css  js  c++  java
  • [转]面向对象的javascript

    转自:http://www.blankyao.cn/blog/oop-javascript.html

    越来越深刻的意识到javascript在web开发中的重要性,于是就用劲的学,整天搞的我晕头转向的…..

    一、引言
    长久以来,JavaScript在Web开发中一直处于被忽视的地位,甚至有相当一部分开发人员对它产生了误解,认为JavaScript只不过是用来完成一些花哨功能的雕虫小技。网络上广为流传的低质量的JavaScript代码对此也起到了推波助澜的作用…很多应用中JavaScript都采用了面向过程的编程方式,代码的可扩展性不好,复制粘贴的代码比比皆是…
    随着Ajax等技术的广泛使用,YUI、Prototype等对JavaScript的应用可谓是淋漓尽致、出神入化。人们才发现原来JavaScript可以实现如此强大的功能,具备如此优雅的架构…


    ———————————-DESCRIPTION——————————————————-

    一、引言
    长久以来,JavaScript在Web开发中一直处于被忽视的地位,甚至有相当一部分开发人员对它产生了误解,认为JavaScript只不过是用来完成一些花哨功能的雕虫小技。网络上广为流传的低质量的JavaScript代码对此也起到了推波助澜的作用…很多应用中JavaScript都采用了面向过程的编程方式,代码的可扩展性不好,复制粘贴的代码比比皆是…
    随着Ajax等技术的广泛使用,YUI、Prototype等对JavaScript的应用可谓是淋漓尽致、出神入化。人们才发现原来JavaScript可以实现如此强大的功能,具备如此优雅的架构…

    二、准备
    1、概念
    JavaScript是一种弱类型语言。包括:
    基本类型:数字Number,字符串String,布尔值Boolean;
    复合类型:对象Object,数组Array;
    工具类型:全局对象Global,日期Date,数学对象Math,正则表达式RegExp,错误对象Error;
    特殊类型:函数Function。

    这里我只想说两点:
    1)基本数据类型的包装对象
    每一个基本数据类型都有一个对应的对象类。可以灵活地实现类型转换。简单地说,JavaScript不仅支持数字、字符串和布尔值这些数据类型,还支持Number、String、Boolean类,这些类是基本数据类型的包装(wrapper)。
    例子:
    var s=”some string”;
    var len=s.length;
    这里,s保存了一个字符串,原始的字符串值是不会改变的。一个新的String对象被创建了,实现了对长度属性的访问,之后它就被销毁了。
    其它例子:

    JavaScript代码

    1. var a=“some string”;
    2. var b=new String(“some string”);
    3. var c=["a","b","c"];
    4. var d=new Array(“a”,“b”,“c”);
    5. alert(typeof a);//string
    6. alert(a instanceof String);//false
    7. alert(a instanceof Object);//false
    8. alert(typeof b);//object
    9. alert(b instanceof String);//true
    10. alert(b instanceof Object);//true
    11. alert(typeof c);//object
    12. alert(c instanceof Array);//true
    13. alert(c instanceof Object);//true
    14. alert(typeof d);//object
    15. alert(d instanceof Array);//true
    16. alert(d instanceof Object);//true

    2)Function类型
    做为JavaScript的一种特殊类型,我们将看到函数在面向对象编程中起到了非常关键的作用。

    2.值和引用
    类型 复制 传递 比较
    数字 值 值 值
    布尔值 值 值 值
    字符串 不可变的 不可变的 值
    对象 引用 引用 引用
    函数 引用 引用 引用
    数组 引用 引用 引用

    例子:

    JavaScript代码

    1. var s1=“hello”;
    2. var s2=“hell”+“o”;
    3. alert(s1==s2);//true
    4. var d1=new Date();
    5. var d2=new Date();
    6. alert(d1==d2);//false

    3.this
    在构造函数中,指代新创建的对象实例;
    在对象的方法被调用时,指代调用该方法的对象实例。

    4.arguments
    arguments属性由解释器创建,用于访问函数对象的每一个参数。

    5.callee,caller
    arguments的callee属性获取对正在执行的Function对象的引用;
    Function对象的caller属性获取正在调用当前函数的父函数对象。

    6.apply,call
    两者都是将函数绑定到其它对象上执行的,区别在于调用方式:
    apply([thisObj[,argArray]])
    call([thisObj[,arg1[,arg2[,[,.argN]]]]])

    7.匿名函数
    (function(a,b){
    return a+b;
    })(1,1);
    等价于:
    function f(a,b){
    return a+b;
    }
    f(1,1);

    8.null,undefined
    null是JavaScript的关键字,表示空值。可以看作Object类型的一个特殊值。
    undefined不是关键字,它是一个全局变量,使用了未定义变量、变量未赋值、void运算符,都会返回“undefined”。

    9.constructor
    从JavaScript1.1开始,每个对象都具有一个constructor属性,它引用的是用来初始化该对象的构造函数。

    10.prototype
    JavaScript1.1引入了原型对象的概念,每一个对象都有一个原型对象,对象可以继承它的原型对象的所有属性和方法。
    要为一个对象的类制定原型对象,需要将构造函数的prototype属性设置为指定的对象。之后,如果用构造函数初始化对象时,会自动将指定的对象作为新创建对象的原型对象。

    注意:
    1)使用原型对象可以减少每个继承对象的内存需求量;
    2)即使属性是在对象被创建后才加到它的原型对象中的,对象也能够继承这些后定义的属性。
    3)当调用一个对象的一个属性时,先在该对象定义内查找该属性,如果没有该属性才到该对象的原型对象中查找,依此类推。

    三、实现
    在面向对象编程中,我们可以把过程编程中的一个个function看作一个个独立定义的类,函数名即为类名。

    JavaScript代码

    1. 1.例子:Circle类
    2. function Circle(radius){
    3. //实例变量
    4. this.r=radius;
    5. }
    6. //静态变量
    7. Circle.PI=3.14159;
    8. //实例方法
    9. Circle.prototype.area=function(){
    10. return Circle.PI*this.r*this.r;
    11. }
    12. //静态方法
    13. Circle.max=function(a,b){
    14. if(a.r>=b.r){
    15. return a;
    16. }
    17. else{
    18. return b;
    19. }
    20. }
    21. //调用
    22. var a=new Circle(2);
    23. var b=new Circle(3);
    24. var c=a.area();
    25. var d=Circle.max(a,b);

    2.继承

    JavaScript代码

    1. 1)一种继承方式
    2. //这里我们将CircleMore类的prototype指向了一个Circle类实例,
    3. //并增加了circumference方法。
    4. function CircleMore(radius){
    5. this.r=radius;
    6. }
    7. CircleMore.prototype=new Circle(0);
    8. CircleMore.prototype.circumference=function(){
    9. return 2*Circle.PI*this.r;
    10. }

    这样能够实现对Circle的继承,但是这里存在一点问题:我们直接把CircleMore类的prototype指向了一个Circle类实例,这样就覆盖了JavaScript提供的原型对象,而且抛弃了给定的constructor属性,这样CircleMore的constructor属性就指向了父类Circle的constructor属性。而且这种方式总是使人感觉怪怪的。

    JavaScript代码

    1. 2)Ajax架构Prototype(区别前面提到的prototype哦)的继承方式
    2. //Prototype框架为Object对象定义了extend方法,
    3. //将source的属性和方法复制到了destination。
    4. Object.extend = function(destination, source) {
    5. for (var property in source)
    6. destination[property] = source[property];
    7. return destination;
    8. };
    9. //调用
    10. function ParentClass(){…}
    11. function SubClass(){}
    12. SubClass.prototype=Object.extend({
    13. newMethod:function(){
    14. alert(“newMethod”);
    15. }
    16. },
    17. ParentClass.prototype
    18. );

    3)如果这里对两个参数交换位置,则是对原对象的开展。
    例子:通过extend方法对String对象进行了扩展

    Object.extend(String.prototype,{
    newMethod:function(){
    alert(”newMethod”);
    }
    });

    3.多态

    Object.extend = function(destination, source) {
    for (var property in source)
    destination[property] = source[property];
    return destination;
    }

    //基类
    function base(){}
    base.prototype={
    initialize:function(){
    this.oninit();//调用了一个虚方法
    }
    }

    //子类SubClassA
    function SubClassA(){}
    SubClassA.prototype=Object.extend({
    //…其它属性方法
    prop:”SubClassA”,
    oninit:function(){
    alert(this.prop);
    }},
    base.prototype
    }

    //子类SubClassB
    function SubClassB(){}
    SubClassB.prototype=Object.extend({
    //…其它属性方法
    prop:”SubClassB”,
    oninit:function(){
    alert(this.prop);
    }},
    base.prototype
    }

    //调用
    var a=new SubClassA();
    var b=new SubClassB();

    a.initialize();//输出”SubClassA”
    b.initialize();//输出”SubClassB”

    四、JavaScript与设计模式

    1.Singleton

    JavaScript代码

    1. function Singleton(){
    2. if(Singleton.caller!=Singleton.getInstance){
    3. throw new Error(“Can not new Singleton instance!”);
    4. }
    5. this.prop=“some string”;
    6. //…
    7. }
    8. Singleton._instance=null;
    9. Singleton.getInstance=function(){
    10. if(this._instance==null){
    11. this._instance=new Singleton();
    12. }
    13. return this._instance;
    14. }
    15. var a=Singleton.getInstance();
    16. var b=Singleton.getInstance();
    17. b.prop=“another string”;
    18. alert(a.prop);//”another string”
    19. alert(b.prop);//”another string”

    JavaScript代码

    1. 2.Factory
    2. function XMLHttpFactory(){}
    3. XMLHttpFactory.createXMLHttp=function(){
    4. if(…){
    5. return new XMLHttpRequest();
    6. }
    7. else if(…){
    8. return new ActiveXObject(“MSXML2.XMLHttp”);
    9. }
    10. }
    11. var xmlhttp=XMLHttpFactory.createXMLHttp();



      前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员、公有实例成员、私有静态成员、公有静态成员和静态类的封装。这次我们来讨论一下面向对象程序设计中的另外两个要素:继承与多态。

      1 又是几个基本概念

      为什么要说又呢?

      在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系。

      1.1 定义和赋值

      变量定义是指用

      var a;

      这种形式来声明变量。

      函数定义是指用

      function a(...) {...}

      这种形式来声明函数。

      var a = 1;

      是两个过程。第一个过程是定义变量 a,第二个过程是给变量 a 赋值。

      同样

      var a = function(...) {};

      也是两个过程,第一个过程是定义变量 a 和一个匿名函数,第二个过程是把匿名函数赋值给变量 a。

      变量定义和函数定义是在整个脚本执行之前完成的,而变量赋值是在执行阶段完成的。

      变量定义的作用仅仅是给所声明的变量指明它的作用域,变量定义并不给变量初始值,任何没有定义的而直接使用的变量,或者定义但没有赋值的变量,他们的值都是 undefined。

      函数定义除了声明函数所在的作用域外,同时还定义函数体结构。这个过程是递归的,也就是说,对函数体的定义包括了对函数体内的变量定义和函数定义。

      通过下面这个例子我们可以更明确的理解这一点:

      alert(a);
      alert(b);
      alert(c);
      var a = "a";
      function a() {}
      function b() {}
      var b = "b";
      var c = "c";
      var c = function() {}
      alert(a);
      alert(b);
      alert(c);

      猜猜这个程序执行的结果是什么?然后执行一下看看是不是跟你想的一样,如果跟你想的一样的话,那说明你已经理解上面所说的了。

      这段程序的结果很有意思,虽然第一个 alert(a) 在最前面,但是你会发现它输出的值竟然是 function a() {},这说明,函数定义确实在整个程序执行之前就已经完成了。

      再来看 b,函数 b 定义在变量 b 之前,但是第一个 alert(b) 输出的仍然是 function b() {},这说明,变量定义确实不对变量做什么,仅仅是声明它的作用域而已,它不会覆盖函数定义。

      最后看 c,第一个 alert(c) 输出的是 undefined,这说明 var c = function() {} 不是对函数 c 定义,仅仅是定义一个变量 c 和一个匿名函数。

      再来看第二个 alert(a),你会发现输出的竟然是 a,这说明赋值语句确实是在执行过程中完成的,因此,它覆盖了函数 a 的定义。

      第二个 alert(b) 当然也一样,输出的是 b,这说明不管赋值语句写在函数定义之前还是函数定义之后,对一个跟函数同名的变量赋值总会覆盖函数定义。

      第二个 alert(c) 输出的是 function() {},这说明,赋值语句是顺序执行的,后面的赋值覆盖了前面的赋值,不管赋的值是函数还是其它对象。

      理解了上面所说的内容,我想你应该知道什么时候该用 function x(..) {…},什么时候该用 var x = function (…) {…} 了吧?

      最后还要提醒一点,eval 中的如果出现变量定义和函数定义,则它们是在执行阶段完成的。所以,不到万不得已,不要用 eval!另外,即使要用 eval,也不要在里面用局部变量和局部方法! 

      1.2 this 和执行上下文

      在前面讨论封装时,我们已经接触过 this 了。在对封装的讨论中,我们看到的 this 都是表示 this 所在的类的实例化对象本身。真的是这样吗?

      先看一下下面的例子吧:

      1. var x = "I'm a global variable!";
      2. function method() {
      3.     alert(x);
      4.     alert(this.x);
      5. }
      6. function class1() {
      7.     // private field
      8.     var x = "I'm a private variable!";
      9.     // private method
      10.     function method1() {
      11.         alert(x);
      12.         alert(this.x);
      13.     }
      14.     var method2 = method;
      15.     // public field
      16.     this.x = "I'm a object variable!";
      17.     // public method
      18.     this.method1 = function() {
      19.         alert(x);
      20.         alert(this.x);
      21.     }
      22.     this.method2 = method;
      23.     // constructor
      24.     {
      25.         this.method1();     // I'm a private variable!
      26.                             // I'm a object variable!
      27.         this.method2();     // I'm a global variable!
      28.                             // I'm a object variable!
      29.         method1();          // I'm a private variable!
      30.                             // I'm a global variable!
      31.         method2();          // I'm a global variable!
      32.                             // I'm a global variable!
      33.         method1.call(this)// I'm a private variable!
      34.                             // I'm a object variable!
      35.         method2.call(this)// I'm a global variable!
      36.                             // I'm a object variable!
      37.     }
      38. }
      39. var o = new class1();
      40. method();       // I'm a global variable!
      41.                 // I'm a global variable!
      42. o.method1();    // I'm a private variable!
      43.                 // I'm a object variable!
      44. o.method2();    // I'm a global variable!
      45.                 // I'm a object variable!

      为什么是这样的结果呢?

      那就先来看看什么是执行上下文吧。那什么是执行上下文呢?

      如果当前正在执行的是一个方法,则执行上下文就是该方法所附属的对象,如果当前正在执行的是一个创建对象(就是通过 new 来创建)的过程,则创建的对象就是执行上下文。

      如果一个方法在执行时没有明确的附属于一个对象,则它的执行上下文是全局对象(顶级对象),但它不一定附属于全局对象。全局对象由当前环境来决定。在浏览器环境下,全局对象就是 window 对象。

      定义在所有函数之外的全局变量和全局函数附属于全局对象,定义在函数内的局部变量和局部函数不附属于任何对象。

      那执行上下文跟变量作用域有没有关系呢?

      执行上下文与变量作用域是不同的。

      一个函数赋值给另一个变量时,这个函数的内部所使用的变量的作用域不会改变,但它的执行上下文会变为这个变量所附属的对象(如果这个变量有附属对象的话)。

      Function 原型上的 call 和 apply 方法可以改变执行上下文,但是同样不会改变变量作用域。

      要理解上面这些话,其实只需要记住一点:

      变量作用域是在定义时就确定的,它永远不会变;而执行上下文是在执行时才确定的,它随时可以变。

      这样我们就不难理解上面那个例子了。this.method1() 这条语句(注意,这里说的还没有进入这个函数体)执行时,正在创建对象,那当前的执行上下文就是这个正在创建的对象,所以 this 指向的也是当前正在创建的对象,在 this.method1() 这个方法执行时(这里是指进入函数体),这个正在执行的方法所附属的对象也是这个正在创建的对象,所以,它里面 this.x 的 this 也是同一个对象,所以你看的输出就是 I’m a object variable! 了。

      而在执行 method1() 这个函数时(是指进入函数体后),method1() 没有明确的附属于一个对象,虽然它是定义在 class1 中的,但是他并没有不是附属于 class1 的,也不是附属于 class1 实例化后的对象的,只是它的作用域被限制在了 class1 当中。因此,它的附属对象实际上是全局对象,因此,当在它当中执行到 alert(this.x) 时,this.x 就成了我们在全局环境下定义的那个值为 “I’m a global variable!” 的 x 了。

      method2() 虽然是在 class1 中定义的,但是 method() 是在 class1 之外定义的,method 被赋值给 method2 时,并没有改变 method 的作用域,所以,在 method2 执行时,仍然是在 method 被定义的作用域内执行的,因此,你看到的就是两个 I’m a global variable! 输出了。同样,this.method2() 调用时,alert(x) 输出 I’m a global variable! 也是这个原因。

      因为 call 会改变执行上下文,所以通过 method1.call(this) 和 method2.call(this) 时,this.x 都变成了 I’m a object variable!。但是它不能改变作用域,所以 x 仍然跟不使用 call 方法调用时的结果是一样的。

      而我们后面执行 o.method1() 时,alert(x) 没有用 this 指出 x 的执行上下文,则 x 表示当前执行的函数所在的作用域中最近定义的变量,因此,这时输出的就是 I’m a private variable!。最后输出 I’m a object variable! 我想不用我说大家也知道为什么了吧?:D

      2 继承和多态

      2.1 从封装开始

      前面我们说了,封装的目的是实现数据隐藏。

      但是更深一层来说,在 javascript 中进行封装还有以下几个好处:

      1、隐身实现细节,当私有部分的实现完全重写时,并不需要改变调用者的行为。这也是其它面向对象语言要实现封装的主要目的。

      2、javascript 中,局部变量和局部函数访问速度更快,因此把私有字段以局部变量来封装,把私有方法以局部方法来封装可以提高脚本的执行效率。

      3、对于 javascript 压缩混淆器(据我所知,目前最好的 javascript 分析、压缩、混淆器就是 JSA)来说,局部变量和局部函数名都是可以被替换的,而全局变量和全局函数名是不可以被替换的(实际上,对于 javascript 脚本解析器工作时也是这样的)。因此,不论对于开源还是非开源的 javascript 程序,当私有字段和私有方法使用封装技术后,编写代码时就可以给它们定义足够长的表意名称,增加代码的可读性,而发布时,它们可以被替换为一些很短的名称(一般是单字符名称),这样就可以得到充分的压缩和混淆。及减少了带宽占用,又可以真正实现细节的隐藏。

      所以,封装对于 javascript 来说,是非常有用的!

      那么在 javascript 实现继承是为了什么呢?

      2.2 为什么要继承

      在其它面向对象程序设计语言中,继承除了可以减少重复代码的编写外,最大的用处就是为了实现多态。尤其是在强类型语言中,尤为如此:

      1、在强类型语言中,一个变量不能够被赋予不同类型的两个值,除非这两种类型与这个变量的类型是相容的,而这个相容的关系就是由继承来实现的。

      2、在强类型语言中,对一个已有的类型无法直接进行方法的扩充和改写,要扩充一个类型,唯一的方法就是继承它,在它的子类中进行扩充和改写。

      因此,对于强类型的面向对象语言,多态的实现是依赖于继承的实现的。

      而对于 javascript 语言来说,继承对于实现多态则显得不那么重要:

      1、在 javascript 语言中,一个变量可以被赋予任何类型的值,且可以用同样的方式调用任何类型的对象上的同名方法。

      2、在 javascript 语言中,可以对已有的类型通过原型直接进行方法的扩充和改写。

      所以,在 javascript 中,继承的主要作用就是为了减少重复代码的编写。

      接下来我们要谈的两种实现继承的方法可能大家已经都很熟悉了,一种是原型继承法,一种是调用继承法,这两种方法都不会产生副作用。我们主要讨论的是这两种方法的本质和需要注意的地方。

      2.3 原型继承法

      在 javascript 中,每一个类(函数)都有一个原型,该原型上的成员在该类实例化时,会传给该类的实例化对象。实例化的对象上没有原型,但是它可以作为另一个类(函数)的原型,当以该对象为原型的类实例化时,该对象上的成员就会传给以它为原型的类的实例化对象上。这就是原型继承的本质。

      原型继承也是 javascript 中许多原生对象所使用的继承方法。

      1. function parentClass() {
      2.     // private field
      3.     var x = "I'm a parentClass field!";
      4.     // private method
      5.     function method1() {
      6.         alert(x);
      7.         alert("I'm a parentClass method!");
      8.     }
      9.     // public field
      10.     this.x = "I'm a parentClass object field!";
      11.     // public method
      12.     this.method1 = function() {
      13.         alert(x);
      14.         alert(this.x);
      15.         method1();
      16.     }
      17. }
      18. parentClass.prototype.method = function () {
      19.     alert("I'm a parentClass prototype method!");
      20. }
      21. parentClass.staticMethod = function () {
      22.     alert("I'm a parentClass static method!");
      23. }
      24. function subClass() {
      25.     // private field
      26.     var x = "I'm a subClass field!";
      27.     // private method
      28.     function method2() {
      29.         alert(x);
      30.         alert("I'm a subClass method!");
      31.     }
      32.     // public field
      33.     this.x = "I'm a subClass object field!";
      34.     // public method
      35.     this.method2 = function() {
      36.         alert(x);
      37.         alert(this.x);
      38.         method2();
      39.     }
      40.     this.method3 = function() {
      41.         method1();
      42.     }
      43. }
      44. // inherit
      45. subClass.prototype = new parentClass();
      46. subClass.prototype.constructor = subClass;
      47. // test
      48. var o = new subClass();
      49. alert(o instanceof parentClass);    // true
      50. alert(o instanceof subClass);       // true
      51. alert(o.constructor);  // function subClass() {...}
      52. o.method1();    // I'm a parentClass field!
      53.                 // I'm a subClass object field!
      54.                 // I'm a parentClass field!
      55.                 // I'm a parentClass method!
      56. o.method2();    // I'm a subClass field!
      57.                 // I'm a subClass object field!
      58.                 // I'm a subClass field!
      59.                 // I'm a subClass method!
      60. o.method();     // I'm a parentClass prototype method!
      61. o.method3();               // Error!!!
      62. subClass.staticMethod();   // Error!!!

      上面这个例子很好的反映出了如何利用原型继承法来实现继承。

      利用原型继承的关键有两步操作:

      首先创建一个父类的实例化对象,然后将该对象赋给子类的 prototype 属性。

      这样,父类中的所有公有实例成员都会被子类继承。并且用 instanceof 运算符判断时,子类的实例化对象既属于子类,也属于父类。

      然后将子类本身赋值给它的 prototype 的 constructor 属性。(注意:这里赋值的时候是没有 () 的!)

      这一步是为了保证在查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,而不是其父类的定义。

      接下来,通过对 o.method1() 调用的结果我们会看到,子类继承来的公有实例方法中,如果调用了私有实例字段或者私有实例方法,则所调用的这些私有实例成员是属于父类的。

      同样,通过对 o.method2() 调用的结果我们看到,子类中定义的实例方法,如果调用了私有实例字段或者私有实例方法,则所调用的这些私有实例成员是属于子类的。

      通过对 o.method() 调用的结果我们看到,定义在父类原型上的方法,会被子类继承。

      通过对 o.method3() 调用的结果我们看到,子类中定义的实例方法是不能访问父类中定义的私有实例成员的。

      最后,通过对 subClass.staticMethod() 调用的结果我们看到,静态成员是不会被继承的。

      2.4 调用继承法

      调用继承的本质是,在子类的构造器中,让父类的构造器方法在子类的执行上下文上执行,父类构造器方法上所有通过 this 方式操作的内容实际上都都是操作的子类的实例化对象上的内容。因此,这种做法仅仅为了减少重复代码的编写。

      1. function parentClass() {
      2.     // private field
      3.     var x = "I'm a parentClass field!";
      4.     // private method
      5.     function method1() {
      6.         alert(x);
      7.         alert("I'm a parentClass method!");
      8.     }
      9.     // public field
      10.     this.x = "I'm a parentClass object field!";
      11.     // public method
      12.     this.method1 = function() {
      13.         alert(x);
      14.         alert(this.x);
      15.         method1();
      16.     }
      17. }
      18. parentClass.prototype.method = function () {
      19.     alert("I'm a parentClass prototype method!");
      20. }
      21. parentClass.staticMethod = function () {
      22.     alert("I'm a parentClass static method!");
      23. }
      24. function subClass() {
      25.     // inherit
      26.     parentClass.call(this);
      27.     // private field
      28.     var x = "I'm a subClass field!";
      29.     // private method
      30.     function method2() {
      31.         alert(x);
      32.         alert("I'm a subClass method!");
      33.     }
      34.     // public field
      35.     this.x = "I'm a subClass object field!";
      36.     // public method
      37.     this.method2 = function() {
      38.         alert(x);
      39.         alert(this.x);
      40.         method2();
      41.     }
      42.     this.method3 = function() {
      43.         method1();
      44.     }
      45. }
      46. // test
      47. var o = new subClass();
      48. alert(o instanceof parentClass);    // false
      49. alert(o instanceof subClass);       // true
      50. alert(o.constructor);  // function subClass() {...}
      51. o.method1();    // I'm a parentClass field!
      52.                 // I'm a subClass object field!
      53.                 // I'm a parentClass field!
      54.                 // I'm a parentClass method!
      55. o.method2();    // I'm a subClass field!
      56.                 // I'm a subClass object field!
      57.                 // I'm a subClass field!
      58.                 // I'm a subClass method!
      59. o.method();                // Error!!!
      60. o.method3();               // Error!!!
      61. subClass.staticMethod();   // Error!!!

      上面这个例子很好的反映出了如何利用调用继承法来实现继承。

      利用调用继承的关键只有一步操作:

      就是在子类定义时,通过父类的 call 方法,将子类的 this 指针传入。使父类方法在子类上下文中执行。

      这样,父类中的所有在父类内部通过 this 方式定义的公有实例成员都会被子类继承。

      用 instanceof 运算符判断时,子类的实例化对象只属于子类,不属于父类。

      查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,不是其父类的定义。

      接下来,通过对 o.method1() 和 o.method2() 调用的结果跟原型继承法的调用结果是相同的,所说明的问题也是一样的,这里不再重复。

      通过对 o.method() 调用的结果我们看到,定义在父类原型上的方法,不会被子类继承。

      通过对 o.method3() 调用的结果我们看到,子类中定义的实例方法同样不能访问父类中定义的私有实例成员的。

      最后,通过对 subClass.staticMethod() 调用的结果我们看到,静态成员同样不会被继承的。

      最后,还有一点,在这个例子中没有体现出来,就是通过调用继承法,可以实现多继承。也就是说,一个子类可以从多个父类中继承通过 this 方式定义在父类内部的所有公有实例成员。

      作为一种弱类型语言,javascript 提供了丰富的多态性,javascript 的多态性是其它强类型面向对象语言所不能比的。

      多态

      重载和覆盖

      先来说明一下重载和覆盖的区别。重载的英文是 overload,覆盖的英文是 override。发现网上大多数人把 override 当成了重载,这个是不对的。重载和覆盖是有区别的。

      重载的意思是,同一个名字的函数(注意这里包括函数)或方法可以有多个实现,他们依靠参数的类型和(或)参数的个数来区分识别。

      而覆盖的意思是,子类中可以定义与父类中同名,并且参数类型和个数也相同的方法,这些方法的定义后,在子类的实例化对象中,父类中继承的这些同名方法将被隐藏。

      重载

      javascript 中函数的参数是没有类型的,并且参数个数也是任意的,例如,尽管你可以定义一个:

      1. function add(ab) {
      2.     return a + b;
      3. }

      这样的函数,但是你仍然可以再调用它是带入任意多个参数,当然,参数类型也是任意的。至于是否出错,那是这个函数中所执行的内容来决定的,javascript 并不根据你指定的参数个数和参数类型来判断你调用的是哪个函数。

      因此,要定义重载方法,就不能像强类型语言中那样做了。但是你仍然可以实现重载。就是通过函数的 arguments 属性。例如:

      1. function add() {
      2.     var sum = 0;
      3.     for (var i = 0i < arguments.lengthi++) {
      4.         sum += arguments[i];
      5.     }
      6.     return sum;
      7. }

      这样你就实现了任意多个参数加法函数的重载了。

      当然,你还可以在函数中通过 instanceof 或者 constructor 来判断每个参数的类型,来决定后面执行什么操作,实现更为复杂的函数或方法重载。总之,javascript 的重载,是在函数中由用户自己通过操作 arguments 这个属性来实现的。

      覆盖

      实现覆盖也很容易,例如:

      1. function parentClass() {
      2.     this.method = function() {
      3.         alert("parentClass method");
      4.     }
      5. }
      6. function subClass() {
      7.     this.method = function() {
      8.         alert("subClass method");
      9.     }
      10. }
      11. subClass.prototype = new parentClass();
      12. subClass.prototype.constructor = subClass;
      13. var o = new subClass();
      14. o.method();

      这样,子类中定义的 method 就覆盖了从父类中继承来的 method 方法了。

      你可能会说,这样子覆盖是不错,但 java 中,覆盖的方法里面可以调用被覆盖的方法(父类的方法),在这里怎么实现呢?也很容易,而且比 java 中还要灵活,java 中限制,你只能在覆盖被覆盖方法的方法中才能使用 super 来调用次被覆盖的方法。我们不但可以实现这点,而且还可以让子类中所有的方法中都可以调用父类中被覆盖的方法。看下面的例子:

      1. function parentClass() {
      2.     this.method = function() {
      3.         alert("parentClass method");
      4.     }
      5. }
      6. function subClass() {
      7.     var method = this.method;
      8.     this.method = function() {
      9.         method.call(this);
      10.         alert("subClass method");
      11.     }
      12. }
      13. subClass.prototype = new parentClass();
      14. subClass.prototype.constructor = subClass;
      15. var o = new subClass();
      16. o.method();

      你会发现,原来这么简单,只要在定义覆盖方法前,定义一个私有变量,然后把父类中定义的将要被覆盖的方法赋给它,然后我们就可以在后面继续调用它了,而且这个是这个方法是私有的,对于子类的对象是不可见的。这样跟其它高级语言实现的覆盖就一致了。

      最后需要注意,我们在覆盖方法中调用这个方法时,需要用 call 方法来改变执行上下文为 this(虽然在这个例子中没有必要),如果直接调用这个方法,执行上下文就会变成全局对象了。

  • 相关阅读:
    回车符和换行符
    UDP ECHO server
    启动LINUX下的TFTP服务器
    WPF版的Dock控件第二版完成
    最近写的一个WPF版的Dock控件
    搜狗开始耍流氓了
    对WebBrowser控件设置代理
    删除Jumplist中的历史记录
    C#中Undo/Redo的一个简易实现
    如何向枚举中添加新值
  • 原文地址:https://www.cnblogs.com/wubiyu/p/1396613.html
Copyright © 2011-2022 走看看