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(虽然在这个例子中没有必要),如果直接调用这个方法,执行上下文就会变成全局对象了。

  • 相关阅读:
    149. Max Points on a Line(js)
    148. Sort List(js)
    147. Insertion Sort List(js)
    146. LRU Cache(js)
    145. Binary Tree Postorder Traversal(js)
    144. Binary Tree Preorder Traversal(js)
    143. Reorder List(js)
    142. Linked List Cycle II(js)
    141. Linked List Cycle(js)
    140. Word Break II(js)
  • 原文地址:https://www.cnblogs.com/wubiyu/p/1396613.html
Copyright © 2011-2022 走看看