JS中範圍和變數提升(hoisting)的深入理解_javascript技巧

來源:互聯網
上載者:User

範圍(Scoping)

對於Javascript初學者來說,一個最迷惑的地方就是範圍;事實上,不光是初學者。我就見過一些有經驗的javascript程式員,但他們對scope理解不深。javascript範圍之所以迷惑,是因為它程式文法本身長的像C家族的語言。我對範圍的理解是只會對某個範圍產生作用,而不會對外產生影響的封閉空間。在這樣的一些空間裡,外部不能訪問內部變數,但內部可以訪問外部變數。

c語言的變數分為全域變數和局部變數,全域變數的作用範圍是任何檔案和函數訪問(當然,對於非變數定義的其他c檔案,需要使用extern關鍵字進行申明,使用static關鍵字也可以將作用範圍限定在當前檔案中),局部變數的作用範圍就是從申明到最近的大括弧涵蓋的塊級範圍。java則無全域變數,有類變數,成員變數和局部變數,作用範圍根據public,protected,private等存取權限有不同的作用範圍,這裡就不多述。

JS範圍有哪些?

在ES5中,js只有兩種形式的範圍:全域範圍和函數範圍。

全域範圍其實是全域對象的範圍,任意地方都可以訪問到(如果沒有被函數範圍覆蓋)。

函數對象範圍跟c的局部變數範圍是不同的,它的範圍是整個函數範圍,不論他是在函數的任意位置申明的!這就是所謂的hoisting,也就是變數提升的概念。不過不著急,下面會專門針對hoisting來進行解釋。

不過,在ES6中,新增了一個塊級範圍(最近的大括弧涵蓋的範圍),但是僅限於let方式申明的變數。
範圍示範:

定義變數時,如果不寫var,比如 i=0,則會被定義為全域變數,範圍為全域範圍,否則為局部變數,範圍為函數範圍。上面第一行的var i=0,之所以說它是全域變數,是因為它已經是在全域區申明的了,並不在函數範圍內,因此跟 i=0 是一樣的。

至於,為什麼結果會是這樣,繼續往下看就知道了。

申明形式

變數聲明:

函數申明:

變數提升(Hoisting)

引出一個問題

下面這段代碼會輸出什麼內容?

這道題我面試過很多人,大多數人都說輸出的是日期。但真實的結果是undefined。為什麼是這樣呢?這裡就引出了一個概念--hoisting,中文的意思就是變數提升。MDN中對變數hoisting的解釋是這樣的:

var hoisting

Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code.

這段話翻譯下來就是

因為變數申明是在任意代碼執行前處理的,在代碼區中任意地方申明變數和在最開始(最上面)的地方申明是一樣的。也就是說,看起來一個變數可以在申明之前被使用!這種行為就是所謂的“hoisting”,也就是變數提升,看起來就像變數的申明被自動移動到了函數或全域代碼的最頂上。

注意:僅僅是申明提升了,定義並不會被提升。

如此,上面這段代碼其實就是下面的形式:

所以,這樣就應該理解了,console輸出的時候,tmp變數僅僅是申明了但未定義,所以輸出應該是undefined。

這裡需要說明的是,雖然所有的申明(包括ES5的var、function,和ES6的function *、let、const、class)都會被提升,但是var、function、function *和let、const、class的的提升卻並不相同!具體原因可以看這裡的說明(大體的意思是雖然let,const,class也被提升了,但是卻並不會被初始化,這時候去訪問他們則會報ReferenceError異常,他們需要到語句執行的時候才會被初始化,而在被初始化之前的狀態叫做temporal dead zone)。我們來看一段代碼就知道了:


這裡a被提升,但因為定義在後,所以輸出undefined

這裡a雖然被提升,但卻報了引用錯誤!

之所以或這樣

因為這樣的原因,推薦的做法是在申明變數的時候,將所用的變數都寫在範圍(全域範圍或函數範圍)的最頂上,這樣代碼看起來就會更清晰,更容易看出來那個變數是來自函數範圍的,哪個又是來自範圍鏈(本文不對此多做解釋,請讀者自行百度,有機會再補充說明)。

重複聲明

上面的輸出其實是:1 2 2。雖然看起來裡面x申明了兩次,但上面說了,js的var變數只有全域範圍和函數範圍兩種,且申明會被提升,因此實際上x只會在最頂上開始的地方申明一次,var x=2的申明會被忽略,僅用於賦值。也就是說上面的代碼實際上跟下面是一致的。

函數和變數同時提升的問題

如果是函數和變數類型同時申明定義了,會發生什麼事情呢?看下面的代碼


A

上面的輸出結果其實是: function foo(){} ,也就是函數內容。

而如果是這樣的形式呢


B

它的輸出卻變成:undefined

為什麼會這樣呢?

原來函數提升分為兩種情況:

      一種:函數申明。就是上面A,function foo(){}這種形式

     另一種:函數運算式。就是上面B,var foo=function(){}這種形式

第二種形式其實就是var變數的聲明定義,因此上面的B輸出結果為undefined應該就能理解了。

而第一種函數申明的形式,在提升的時候,會被整個提升上去,包括函數定義的部分!因此A跟下面的這種方式是等價的!

原因是因為:1、函式宣告被提升到最頂上;2、申明只進行一次,因此後面var foo='i am text'的申明會被忽略。

並且函數申明的優先順序優於變數申明,所以以下形式的輸出,同樣是函數內容:

總結

要徹底理解JS的範圍和Hoisting,只要記住以下三點即可:

      1、所有申明都會被提升到範圍的最頂上

      2、同一個變數申明只進行一次,並且因此其他申明都會被忽略

      3、函式宣告的優先順序優於變數申明,且函式宣告會連帶定義一起被提升

注意:

通過with語句,可以臨時改變運行期內容相關的範圍鏈,此時的對非var定義的變數進行訪問,會首先訪問with中對象的屬性,然後才會向上順著範圍鏈向上檢查該屬性。

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能有所協助,如果有疑問大家可以留言交流。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.