Javascript之『變數提升』
變數宣告恐怕是我們日常開發中最最經常遇到的了,那今天我們就來總結下現在js一共有哪幾種變數宣告的方式以及各個宣告方式的特點。
ES5變數宣告方式有var, function。
通過這兩種方式宣告的變數特點就是具有“變數提升”的效果,一般想法是一個變數是先聲明後使用,然而如果採用var 或者function宣告的變數和函式(函式表示式不會提升)具有提升的效果。
下面詳細說明變數提升究竟是怎麼產生的。
程式碼在引擎中執行之前都會經歷詞法分析階段 ,這個階段會將由字元組成的字串分解成若干(對程式語言而言)有意義的程式碼塊,而這些小的程式碼塊我們可以稱他們為詞法單元 ,舉個栗子給大家,比如:var age = 27;這一句通常會被分解為var、age、=、27、;空是否會被當做詞法單元取決於空格在這門語言中是否有意義。接下來是語法分析 階段會將這些詞法單元解析成一個樹結構我們將這個樹結構稱之為“抽象語法樹”。然後編譯器會進行如下處理,首先遇到var age,編譯器會詢問當前作用域是否已近存在一個改名字的的變數,如果是的,編譯器就會忽略該宣告,如果不是的話就會要求在當前作用域的集合中宣告一個新的變數,並且為這個變數命名為age。但是不會立刻給他賦值,賦值是在執行的時候才做的事情。編譯階段 實際會吧所有的var宣告的變數提升到前面。即var age = 27; 可以近似的看成var age;(編譯階段)age = 27;(執行階段)。所以就會產生“變數提升”的效果了。
關於變數提升需要注意這幾點:
在js中用var ,function宣告的變數都將被提到函式的最頂部。(但是不會初始化)
函式宣告的優先順序大於變數宣告的優先順序(function > var )
在函式內部變數提升的優先順序會小於函式引數(函式引數 > 函式內部變數提升)
下面輔以例子來具體說明
console.log(a) var a = 3;
執行結果如下:
undefined
實際上等同於執行下列程式碼:
var a; console.log(a) a = 3;
打印出a的時候,a已近宣告(變數提升的效果)但是並未賦值,自然就是undefined了,如果沒有變數提升的效果此處就會報一個reference erro的錯誤型別了。
再看如下程式碼:
console.log(f) f() var f = 27; function f(){ console.log('I am function f') } console.log(f); f = 2; f();
看看執行結果:
ƒ f(){ console.log('I am function f') } I am function f 27 Uncaught TypeError: f is not a function
下面我們來分析一下為什麼是這個結果:
先var 和function都具有變數提升且根據我們上面的第二條原則function > var 所以第一次列印console.log(f)此時的f是一個函式;
接下來是執行f()自然就會得到f函式內列印的字串'I am function f'再接下來就是給f賦值了此時的f = 27;列印f就會得到數字27,然後又將f的值改為2,再執行f()而此時f已近不是一個函數了,所以控制檯會抱一個typeError的錯誤,表示我們對f的用法錯了(關於各個錯誤型別我們後面會有一篇文章專門說明)。
下面再看看我們所說的第三點:
var age = 0; function foo(age) { console.log(age); var age = 20; } foo(27) console.log(age)
在函式foo中此時傳入的引數age=27,根據我們上述的第三點即函式引數 > 函式內部變數提升。所以在函式內部打印出的age是傳入的引數27,最後一句打印出的age自然就是0了。
有關於函式表示式是否會有變數提升,我看了很多人寫的部落格是說函式表示式並不會有變數提升,我並不贊同這個觀點,我覺得函式表示式也會產生變數提升,事實上只要是用var 與function宣告的變數都會產生變數宣告,但切記用var 宣告的變數不會立馬賦值。看如下程式碼。
console.log(foo) var foo = function () { console.log('I am a function foo') }
undefined
實際上在這兒執行的程式碼等同下面的程式碼:
var foo; console.log(foo) foo = function () { console.log('I am a function foo') }
所以函式表示式也同樣有變數提升的效果。