Python's Archiver

為方便港臺同胞閱覽,Python中國特別推出簡繁體內容轉換功能

xieaotian 发表于 2008-8-18 12:07

JavaScript 面向对象程序设计(下) - 继承与多态

前面我们讨论了如何在 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 所在的类的实例化对象本身。真的是这样吗?

先看一下下面的例子吧:

var x = "I'm a global variable!";
function method() {
    alert(x);
    alert(this.x);
}
function class1() {
    // private field
    var x = "I'm a private variable!";
    // private method
    function method1() {
        alert(x);
        alert(this.x);
    }
    var method2 = method;
    // public field
    this.x = "I'm a object variable!";
    // public method
    this.method1 = function() {
        alert(x);
        alert(this.x);
    }
    this.method2 = method;
    // constructor
    {
        this.method1();     // I'm a private variable!
                            // I'm a object variable!
        this.method2();     // I'm a global variable!
                            // I'm a object variable!
        method1();          // I'm a private variable!
                            // I'm a global variable!
        method2();          // I'm a global variable!
                            // I'm a global variable!
        method1.call(this); // I'm a private variable!
                            // I'm a object variable!
        method2.call(this); // I'm a global variable!
                            // I'm a object variable!
    }
}
  
var o = new class1();
method();       // I'm a global variable!
                // I'm a global variable!
o.method1();    // I'm a private variable!
                // I'm a object variable!
o.method2();    // I'm a global variable!
                // 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

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.