調用對象位於做用域鏈的前端,局部變量(在函數內部用var聲明的變量)、函數參數及Arguments對象都在函數內的做用域中——這意味着它們隱藏了做用域鏈更上層的任何同名的屬性。
2010年9月14日,我去參加網易網頁工程師招聘會,應聘JS工程師職位。有幸參加筆試,而後有幸栽在筆試,呵呵。廢話少說,抓出音響極深的一題從新研究研究。
題目大概是:寫出以下代碼的輸出結果並進行分析前端
var tt = 'aa'; function test(){ alert(tt); var tt = 'dd'; alert(tt); } test();
「太簡單了!」這是我當時看到這個題目是的第一想法,因而輕率答題竟成個人致命之傷。個人答案是——aa和dd,解析:第一次輸出全局變量的結果,而後局部變量tt覆蓋全局變量所引用的值,因此第二次輸出結果是dd。
任何人見我如此做答,都會認爲我是在掃盲——想法及其幼稚(我也這麼認爲)!
網易啊,怎麼可能會滿意於這種答案!
正確的答案應該是:undefined和dd
爲何第一次alert的結果是undefined呢?要解釋得清楚明白鬚要用到Javascript的詞法做用域。
Javascript中的函數「在定義它們的做用域裏運行,而不是在執行它們的做用域裏運行」,這是權威指南里抽象而精闢的總結。
Javascript的邏輯默認在一個全局做用域中執行,如以上程序段中的「var tt='aa';」就是定義一個全局做用域的全局變量(若是以上代碼段不是摘自某個函數鏈的話)。而test()函數內部的邏輯必須在原有的做用域(全局 做用域)鏈再添加test函數自己的做用域(局部性)——這些思想幾乎在每一種語言中都是如此定義的,然而Javascript做用域鏈的特別之處在於函 數內部可以嵌套函數的定義(這是閉包的基礎。注:在JS中函數是惟一形式的代碼做用域)
嵌套的內部函數能夠調用外部函數(被嵌套的函數)的變量和其餘嵌套函數(函數是一種數據)。若是是在外部函數內調用嵌套函數,那麼調用對象不變,當 外部函數執行完畢後全部數據(包括外部函數和嵌套的內部函數)都將被垃圾回收機制收集——這一點還不能體現出‘閉包'的精華。有一種狀況,就是 Javascript容許外部調用嵌套的內部函數,即便被嵌套函數已經被‘垃圾收集'——最多見的就是在‘某個函數'中用其嵌套的內部函數定義某些元素的 響應事件,頁面載入的時候被嵌套函數(‘某個函數')已經執行完畢(被垃圾回收),但當事件觸發的時候仍然會有響應的動做,並且響應函數中還可能調用到在 被嵌套函數(‘某個函數')中定義的變量最終值(不是被垃圾回收了嗎?)。
關於閉包的知識和示例有不少資料可供查詢,我不想敘述。
本文的重點是如下很是重要的細節:
調用對象位於做用域鏈的前端,局部變量(在函數內部用var聲明的變量)、函數參數及Arguments對象都在函數內的做用域中——這意味着它們隱藏了做用域鏈更上層的任何同名的屬性。
即,在以上程序片斷中,test函數內部的「var tt='dd'」將會導致「var tt='aa'」在test函數被調用時徹底被隱藏。並且,tt是在第一個alert語句以後定義,因此在調用到第一個alert時,tt是尚未被賦值 的。這樣說可能會清楚一點,即,在定義test函數時,當定義第一個alert(tt)時,這裏會記錄tt是做用域鏈中的一個變量但不會記錄它(tt)的 值,函數定義完畢後tt就添加到做用域裏,因此第一個alert語句可以找到該做用域裏的tt(即,至關於找到一個已經在函數內部聲明,但未被賦值的 tt)。
以上程序片斷的執行結果與如下片斷的結果相同:閉包
var tt = 'aa'; function test(){ var tt; alert(tt); tt = 'dd'; alert(tt); } test();
Javascript的做用域不可簡單的用C++等語言的思惟來理解啊!C++在調用函數以前必須先聲明或定義,而Javascript不必。在 Javascript中能夠先調用函數,後再定義(不用在調用以前做任何聲明)。由於在調用函數時,Javascript是向做用域鏈要函數的定義(函數在定義它們的做用域裏運行,而不是在執行它們的做用域裏運行)
如以上代碼寫成:函數
var tt = 'aa'; test(); //先調用後再定義 function test(){ alert(tt); //undefined var tt = 'dd'; alert(tt); //dd }