作者: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() {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中的私人變數無法在其聲明函數外訪問,例如:
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對私人成員的值進行讀寫。最後卻演變成為一個白馬是不是馬的哲學討論,真是汗顏。