日期:2007.8.3
在我前面一篇文章《在JavaScript中使用面向对象》中我们介绍了MSDN的一篇文章《使用面向对象的技术创建高级 Web 应用程序》,作者简单介绍了JavaScript面向对象的一些关键技术,但是作者在讲到闭包概念的时候犯了一个明显的错误:“正常情况下,无法从函数以外访问函数内的本地变量。函数退出之后,由于各种实际原因,该本地变量将永远消失”详见原文。事实上这段描述是错误的。
请先看如下代码:
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 应用程序》的作者认为是由于方法的定义才使局部变量存活下来,这一点是不正确的,具体我们前面已经分析过了,如果你仍有疑问,那么再仔细研究下面的代码:
this.getName = function() {debugger; return 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中的私有变量无法在其声明函数外访问,例如:
{
var ttt;
}
永远不能在{}外部试图访问ttt。
现在我们更加深入的理解了变量作用域在JavaScript中的特点。
前面讲了高级编程语言中属性的种种好处,又研究了JavaScript对私有变量的保护,那么您对JavaScript中属性的实现应该非常清楚了,这里引用《使用面向对象的技术创建高级 Web 应用程序》文中的一段示例代码:
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对私有成员的值进行读写。最后却演变成为一个白马是不是马的哲学讨论,真是汗颜。