OTHER PAGES

WELCOME

Move your mouse onto the image to see the bravo CSS3 Transition!

2014年10月7日 星期二

在JavaScript函數中屏蔽外部變數

眾所周知,JavaScript的閉包(Closure)之所以強大,在於它可以捕捉當下執行環境的變數到閉包裡面來用。這給予了整個程式語言靈活的特性(不過從Schema學來的)。不過,若是要深入解釋閉包,可能要花非常多的精力與時間(參看:http://stackoverflow.com/a/111111/2281355),因此這裡省略。
讓我們把重點放在閉包的應用上,考慮以下例子:
(function(){
  var foo="world";
  var bar=function(){
    return "Hello, "+foo;
  }
  return bar();
})()   //Hello, world
 可以看到,當在bar裡面找不到foo的定義時,就往上一層找到foo="world"。假如還是找不到,就繼續往上找,直到真的找不到的時候,拋出Error。這是因為JavaScript在執行每個函式的時候,會先從執行到的地方建立一個scope chain,由裡到外把變數參考丟進去,這也是為什麼盡可能不要宣告全域變數,因為它們永遠在scope chain最外面,存取它們會比較慢。
全域變數有個特性:隨處都可以存取。這是個很方便的特性,不過在某些情況下卻會造成問題。例如我們有時候要直接執行使用者或其他網站提供的JavaScript,卻要防範跨網站指令碼(cross-site script,XSS),或只是單純想要一個「乾淨的空間」(沙盒),那要怎麼做呢?顯然,如果我們拿的到最上層的變數,我們只需要在scope最前面,用其鍵值(key)覆蓋掉全域變數的參考,然後再將最上層變數的參考也一起蓋掉即可。下面是我想到的做法,假設我們是在瀏覽器的環境下運行下列的程式碼,而最上層變數通常為window。

將每一行指令都用try ... catch包起來,是因為鍵值允許任意字串,但是宣告不行,所以要避免用特殊符號宣告變數造成錯誤。然而,若覆蓋掉最上層的物件,理論上我們也無法直接利用鍵值取得其屬性,因此宣告失敗了也沒關係。
如此一來,在isolate函數內部就完全取不到任何關於最上層物件的參考了!然而,這卻未消去JavaScript的其他功能,比如基本類型、陣列、物件、函式等等,只不過,如果函數本身沒有洩漏任何內容的話,他們在內部的運作不會影響到外面,如被隔離了一樣。不過,依然要注意效能問題,由於是呼叫eval函式,在同一個地方大量宣告變數,這個動作是非常慢的(一次大約幾個毫秒,視全域變數有多少而定)。一個解決的辦法是把產生出的程式碼快取起來,但是這樣依然很慢,因此只有隔離帶來的效益足夠大,才推薦使用這個方法。
張貼留言