JavaScript變量作用域之殤

果你愛上了JavaScript這門詭異的語言,那我相信你一定在與其戀愛期間飽受了其變量作用域所引發的一系列問題的不少摧殘。對於任何一門編程語言,變量作用域都是一個關切的話題。正如David Herman在《Effective JavaScript》中的形象比喻,“Scope is like oxygen to a programmer”。當你“呼吸順暢”的時候,你並不會意識到變量作用域的重要性;然而當你“呼吸受阻”的時候,你便會體會到它的輕重高低。

全局作用域

絕大多數編程語言都有全局作用域的概念。全局作用域是指常量、變量、函數等對象的作用範圍在整個應用程序中都是可見的。對於不同的編程語言,全局作用域承擔着不同的角色,也因此遭受了不少的罵名。但對於JavaScript,我並不認爲它一無是處。我們要做的便是理解它並正確地使用它。

考慮下這樣一個場景。Bill和Peter在同一家公司工作,他們的薪水由兩部分組成:a和b。以下是表示他們薪水組成的數據結構。

 

輸出的結果並不是你口算的4750,而是2500。這是因爲變量i、n和sum都是全局變量,在執行salary(emps0)之後i的值變爲了2,再回到averageSalary函數的循環體中時emps數組已然越界,最終sum的值只計算了emps數組中的第一個元素。

如果這樣的全局作用域問題並不會困擾你,那下面的問題似乎應當引起你的一些警覺。因爲與此相比,它有點意想不到。

問題並不是出在交換數組元素上,而是我們無意間創建了一個全局的變量temp。這要完全歸功於JavaScript的語言規範——JavaScript會將未使用var聲明的變量視爲全局變量。慶幸的是,我們可以藉助於類似Lint這樣的代碼檢測工具幫我們儘早地發現這類問題。

雖然全局變量有很多問題,然而它在支撐JavaScript模塊之間數據共享、協同合作方面確實承擔了重要的角色。此外,程序員在某些不支持ECMAScript 5的環境中利用其特性檢查的功能來填補一些ES5特有的特性確實受益良多。

詞法作用域和動態作用域

在程序設計語言中,變量可分爲自由變量與約束變量兩種。簡單來說,局部變量和參數都被認爲是約束變量;而不是約束變量的則是自由變量。 在馮·諾依曼計算機體系結構的內存中,變量的屬性可以視爲一個六元組:(名字,地址,值,類型,生命期,作用域)。地址屬性具有明顯的馮·諾依曼體系結構的色彩,代表變量所關聯的存儲器地址。類型規定了變量的取值範圍和可能的操作。生命期表示變量與某個存儲區地址綁定的過程。根據生命期的不同,變量可以被分爲四類:靜態、棧動態、顯式堆動態和隱式堆動態。作用域表徵變量在語句中的可見範圍,分爲詞法作用域和動態作用域兩種。

在詞法作用域的環境中,變量的作用域與其在代碼中所處的位置有關。由於代碼可以靜態決定(運行前就可以決定),所以變量的作用域也可以被靜態決定,因此也將該作用域稱爲靜態作用域。在動態作用域的環境中,變量的作用域與代碼的執行順序有關。下面這段代碼的輸出會是什麼?

如果你的回答是1, 2或3, 1都沒有錯,因爲這取決於該段代碼所處的環境。如果處於詞法作用域中,答案便是1, 2;如果處於動態作用域中,答案便是3, 1。

詞法作用域允許程序員根據簡單的名稱替換就能推導出對象引用,例如常量、參數、函數等。這使得程序員在編寫模塊化的代碼是多麼的得心應手。同時,這可能也是動態作用域令人感覺到晦澀的原因之一。詞法作用域最早可以追溯到ALGOL語言。儘管最早的Lisp解釋器和早期的Lisp變種都採用動態作用域,但隨後的動態作用域語言都支持了詞法作用域。Common Lisp和Perl的語言演化就是最好的證明。JavaScript和C都是詞法作用域語言。不過值得一提的是,不像JavaScript,深受ALGOL語言影響的C語言並不支持嵌套函數。這對後來的C族語言影響深遠。除了晦澀難懂之外,現代程序設計語言很少支持動態作用域的原因是動態作用域使得引用透明的所有好處蕩然無存。

臭名昭著的with語句

如果你還在使用類似下面的代碼爲with語句找藉口,那這正好是放棄它的真正原因。

 

JavaScript會將with語句中的對象插入到詞法作用域的鏈表頭。這將使得status函數非常脆弱。例如,

第二次status函數調用並不會得到預期的結果“Status:connected”而是“Status:widget info”。這是因爲在第二次status函數調用之前,我們修改了widget的原型對象(增加了一個info屬性)。這將導致status函數的參數info會被處於詞法作用域鏈表頭的widget對象的原型對象中的info屬性所屏蔽。除此之外,with語句還會導致性能問題。這與在採用鏈地址法解決散列衝突的散列表中查找關鍵字是異曲同工的。下面是修正的代碼。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章