· 本文地址: http://www.laruence.com/2008/07/28/210.html
· 轉載請註明出處
在js中,作用域的概念和其他語言差不多, 在每次調用一個函數的時候 ,就會進入一個函數內的作用域,當從函數返回以後,就返回調用前的作用域,但js的實現方式卻和一般語言不同,並非用“堆棧”方式,而是使用列表,具體過程如下(ECMA262中所述):
在一個函數對象被創建的時候, 會創建一個活動對象(也就是一個對象),然後對於每一個函數的形參,都命名爲該活動對象的命名屬性:
var func = function(lps, rps){ ........ }對於上面的列子,會創建一個活動對象(假設爲aObj),給這個對象添加倆個命名屬性aObj.lps, aObj.rps; 然後還會創建一個arguments對象,這個對象以數組的方式保存調用參數,還有callee等其他參數;然後將arguments也作爲活動對象(假設爲aObj)的一個同名命名屬性(arguments);還有就是,對於每一個這個函數申明,或者定義的變量,都作爲該活動對象的同名命名屬性。內部函數也是一樣。
在真正的調用時刻,會做一個叫做變量實例化的過程,也就是將調用參數賦值給形參數,對於缺少的調用參數,賦值爲undefined。
接下來,談談作用域, 在js中,作用域的實現,是通過一個列表[[scope]]。對於每一個js的函數對象,都維護一個內部的對象列表[[scope]],這個列表由上面提到的一個個活動對象組成。
當調用一個函數的時候,這個函數的活動對象列表,將保存該函數作用域(調用者的作用域)列表,然後將對於這個被調用函數所生成的活動對象,置於[[scope]]的最前端。
當發生標識符解析的時候, 會逆向查詢這個[[scope]]列表的每一個活動對象的屬性,如果找到同名的就返回。找不到,那就是這個標識符沒有被定義。
function a(){ .... } function b(){ a(); }對於上面的列子,當調用b()的時候, b的[[scope]]由: 全局活動對象->b的活動對象組成。
而當b()在內部調用a()的時候,a()的[[scope]]由:全局活動對象->b的活動對象->a的活動對象組成。
這樣,就構成了js自己的作用域的機制。。
接下來談談閉包,ECMA262沒有規定垃圾回收機制的具體實現,但一般來說,垃圾回收機制的原理都是,如果一個對象沒有再
被引用,那麼這個對象就會成爲垃圾回收的目標。
接下來結合前面講的,分析如下代碼:
function a(){ return funtion b(){ .....};; } var ref = a();
當調用a()的時候,a返回了一個叫做b的函數對象的引用。 當從a調用返回以後,因爲ref引用了b(),所以在b()的[[scope]]上的,已經被變量實例化了的a的活動對象就不會被垃圾回收,那麼這樣,當在外部直接調用ref所引用的b時,就可以訪問到b()函數外層的a()函數對象已經被實例化了的各個參數。
這樣就形成了一個閉包。也可以理解爲一個不會被回收的函數對象列表,不過要注意的時候,在這個列表中的a()的活動對象是已經被實例化了的。
現在根據前面所講的這些,討論一個問題,就是對於js對象編程中所實現的"私有成員":
function class(){ var private; this.public; }做過Javascript面對對象編程的朋友一定知道,對於上面的代碼,private變量是一個私有的變量,而public變量是共有的,那麼他們具體是怎麼實現的呢?
結合上面的討論,我們來分析一下,
對於private, 它會被作爲class函數對象的活動對象的一個屬性,那麼當class函數對象(構造函數)執行結束以後,該活動對像就會被回收,那麼自然,這個private也就不能再被外部訪問了,也就是模擬了私有變量。
而對於this.public,我們知道this在js中是一個關鍵字,而不是一個標識符,它指向調用者的引用, 比如:
var objRef = new Class();這個時候,this指向的是 objRef,所以,,,,,,很顯然,這個時候public就可以通過objRef來訪問了,公有的屬性了。。。
當然,你也許會發現,如果是這樣的話,每次new一個對象的時候,這種形式的共有對象,每次都會new一個新的對象,會很浪費資源,所以,最好還是通過函數對象的原型來創建公有的屬性。