Truly
写精彩代码 品暇逸人生
作者:Truly
日期:2007.8.3

在我前面一篇文章《
在JavaScript中使用面向对象》中我们介绍了MSDN的一篇文章《
使用面向对象的技术创建高级 Web 应用程序》,作者简单介绍了JavaScript面向对象的一些关键技术,但是作者在讲到闭包概念的时候犯了一个明显的错误:“正常情况下,无法从函数以外访问函数内的本地变量。函数退出之后,由于各种实际原因,该本地变量将永远消失”详见原文。事实上这段描述是错误的。

请先看如下代码:
<script>
function Test(abc)
{
this.g = function(){debugger;};
}

var p = new Test(2);
p.g();
</script>

如果你启用了IE的调试功能,并安装了脚本调试器,例如VS,那么你在程序提示调试的时候进入调试,此时你可以醒目的发现abc依然存在,并且完好的保存了正确的值,而并非永远消失。但是这也不是我本文要讨论的重点,只是希望大家以后能够多动手,多实践,像MS ASP.NET AJAX 团队的软件设计工程师都会犯这种错误,更何况诸位呢?

本文要讨论的是面向对象编程中常用的属性,但是在JavaScript中属性则无法像高级编程语言那样可以直接使用,看起来更像方法,这种实现方式也有人称之为闭包,但本文以属性相称。

属性是对私有变量的一种保护手段,同时提供了像public变量一样的使用效果,近代的高级编程语言例如C#和Java都支持了属性这一特点。

我们知道,函数的入口参数被声明为该函数的本地变量,而对于本地变量,像我前面《在JavaScript中使用面向对象》关于全局变量和局部变量中描述的那样,由于其作用域仅限于函数内部,所有无法在外部对其进行访问,例如p.abc不会返回p内部的abc变量。这一点跟高级编程语言完全一致,你无法在类外部访问其private变量,但是我们可以借助public方法来返回私有变量。所以高级编程语言如Java,C#等中属性的作用,就是保护私有变量。像C#这门语言,属性最终会由编译器编译为get_属性名()这样的方法,当我们使用某个属性时,实质上是调用一个方法。

使用面向对象的技术创建高级 Web 应用程序》的作者认为是由于方法的定义才使局部变量存活下来,这一点是不正确的,具体我们前面已经分析过了,如果你仍有疑问,那么再仔细研究下面的代码:
function Person(name, age) {
    
this.getName = function() {debuggerreturn 1; };
}
var o = new Person(1,2);
o.getName();    
// 进入调试后发现name=1
var t = new Person(2,4);
t.getName();    
// 进入调试后发现name=2
o.getName();    // 进入调试后发现name=1,并未受到其它实例的影响

对于这个问题,我起初也认为是因为变量有引用才没被销毁,最后证明局部变量在对象销毁前其内部的变量不会销毁。

同时那篇文章中另外一段也是不准确的:
注意,这些私有成员与我们期望从 C# 中产生的私有成员略有不同。在 C# 中,类的公用方法可以访问它的私有成员。但在 JavaScript 中,只能通过在其闭包内拥有这些私有成员的方法来访问私有成员(由于这些方法不同于普通的公用方法,它们通常被称为特权方法)。因此,在 Person 的公用方法中,仍然必须通过私有成员的特权访问器方法才能访问私有成员

关于这一点,他的表述相当模糊,事实上我们可以这样理解:

在C#中我们可以在类的任何方法中访问类的私有成员变量,
而在JavaScript中,只能使用在function方式中定义的方法对私有成员访问,而无法在prototype方式定义的方法中访问。

如果这样讲你还不能理解的话,那么还可以这样理解,在JavaScript中的私有变量无法在其声明函数外访问,例如:

function Person()
{
    
var ttt;
}

永远不能在{}外部试图访问ttt

现在我们更加深入的理解了变量作用域在JavaScript中的特点。

前面讲了高级编程语言中属性的种种好处,又研究了JavaScript对私有变量的保护,那么您对JavaScript中属性的实现应该非常清楚了,这里引用《使用面向对象的技术创建高级 Web 应用程序》文中的一段示例代码:

function Person(name, age) {
    
this.getName = function() { return name; };
    
this.setName = function(newName) { name = newName; };
    
this.getAge = function() { return age; };
    
this.setAge = function(newAge) { age = newAge; };
}

关于属性在实际中的应用及其优点,将在我下一篇文章介绍自定义事件中讲解。

后记:本来打算在这里讲述如何在JavaScript中实现面向对象中的一些特性,比如用“属性”这一现代编程概念体现的对象的封装性:不直接操作类的数据内容,而是通过访问器进行访问,即借助于get和set对私有成员的值进行读写。最后却演变成为一个白马是不是马的哲学讨论,真是汗颜。

posted on 2007-08-03 21:23  Truly  阅读(4774)  评论(16编辑  收藏  举报