很久以前遇到過一個面試題目,的的確確是面試官問我的問題,下面是這個問題的代碼部分。由於年少無知,沒有回答上,被無情pass了。
var u ='hello world'; ;(function(){ alert(u); var u = 'bonjour la monde'; })(); //請問alert的結果是什么?.
一開始毫不猶豫地想到 alert出來的是hello world; 面試官一臉無奈看着我,聳聳肩,我就大概知道被鄙視了。其實結果是undefined,但是一直沒想通這樣一個結果。后來才明白,JavaScript解析過程分為兩個階段,一個是預解析階段,另外一個就是執行階段。
執行階段我們應該都很明白是什么意思
var u = 'hello world'; alert(u);
這段代碼的結果是在瀏覽器中彈出出hello world不錯,很簡單,同樣簡單的還有下面的代碼
alert(u); var u = 'hello world';
結果是undefinded。因為u是在alert之后初始化的。所以按照從上到下的執行順序來說,這樣的結果是我們能夠接受的。那么下面的代碼呢?
u(); function u() { alert('hello world'); }
結果是alert彈出hello world;這是為什么呢?u()明顯地是在function函數上面先調用的,為什么函數能夠執行呢?
其實在執行代碼之前,瀏覽器會對js代碼做預解析,解析的原則是這樣的: 對該環境內用var聲明變量進行提升到頂部的動作,並給它一個初始值undefined,同樣,瀏覽器也會對function做提升動作,但是值是function返回的值,而不是undefined。並且按照命名唯一的原則,會將后聲明的函數覆蓋之前聲明的同名函數上。如果你對上述說明不是很明白,我們可以換一種說法:瀏覽器在刷新的時候會悄悄地去執行。界面的js代碼,不過它的執行方式是把所有在環境(window 或者 function)內聲明的var 變了全部置頂並初始化為undefined。所以我們一開始面試官出題的代碼在預解析之后可以等同於以下代碼。
var u = 'hello world'; ;(function(){} var u;//或者var u = undefined; alert(u); u = 'bonjour la monde'; })();
有兩個變量u,第一個是全局變量u,它是在window這個大環境內聲明的,第二個是局部變量,它是在匿名函數function內聲明的,因為匿名函數內包含着一個塊級作用域,所以我們也可以說匿名函數內包含着一個不同於window的另一個環境。按照我們在上面提到的解析原則,在這個環境內的var變量都會提升到頂部,並且初始化為undefined,所以代碼看起來就是上面的那個樣子。而解析原則也會對函數提升,但是初始值不是undefined而是該函數本身,也就是說不會改變。那么下面兩段代碼可以說是同樣的效果:
/解析前的函數 (function(){ say(); function say() { alert('hello world'); } }); //解析后的同效函數 (function(){ var say = function() { alert('hello world'); } say(); });
解析原則還包括對同名函數的覆蓋,下面兩段代碼可以說明解析前后的狀態。
//預解析前 (function () { function say() { alert('hello world'); } say() function say() { alert('bonjour la monde'); } }) //解析后的等效函數 (function(){ var say = function() { alert('hello world') } var say = function() { alert('bonjour monde') } say(); });
現在,相信你應該對js的預解析過程有清楚的了解了。需要注意的是,用var和函數命名(function someFun())的方式聲明一個函數都沒有問題,這取決與項目的需要或者代碼的規范或者個人的喜好,但是遇到以上的問題比如用var聲明,那么在此之前你是不能夠調用它的,而用function name() 方式命名,在同一個環境或者作用域內,你可以在任何地方調用它。