下面的程序是什么結(jié)果?
那么下面這個(gè)呢?
嚇你一跳吧?發(fā)生了什么事情?這可能是陌生的,危險(xiǎn)的,迷惑的,同樣事實(shí)上也是非常有用和印象深刻的javascript語言特性。對(duì)于這種表現(xiàn)行為,我不知道有沒有一個(gè)標(biāo)準(zhǔn)的稱呼,但是我喜歡這個(gè)術(shù)語:“Hoisting (變量提升)”。這篇文章將對(duì)這種機(jī)制做一個(gè)拋磚引玉式的講解,但是,首先讓我們對(duì)javascript的作用域有一些必要的理解。
Javascript的作用域
對(duì)于Javascript初學(xué)者來說,一個(gè)最迷惑的地方就是作用域;事實(shí)上,不光是初學(xué)者。我就見過一些有經(jīng)驗(yàn)的javascript程序員,但他們對(duì)scope理解不深。javascript作用域之所以迷惑,是因?yàn)樗绦蛘Z法本身長的像C家族的語言,像下面的C程序:
對(duì)于很多C,c++,java程序員來說,這不是他們期望和歡迎的。幸運(yùn)的是,基于javascript函數(shù)的靈活性,這里有可變通的地方。如果你必須創(chuàng)建臨時(shí)的作用域,可以像下面這樣:
變量聲明,命名,和提升
在javascript,變量有4種基本方式進(jìn)入作用域:
•1 語言內(nèi)置:所有的作用域里都有this和arguments;(譯者注:經(jīng)過測(cè)試arguments在全局作用域是不可見的)
•2 形式參數(shù):函數(shù)的形式參數(shù)會(huì)作為函數(shù)體作用域的一部分;
•3 函數(shù)聲明:像這種形式:function foo(){};
•4 變量聲明:像這樣:var foo;
函數(shù)聲明和變量聲明總是會(huì)被解釋器悄悄地被“提升”到方法體的最頂部。這個(gè)意思是,像下面的代碼:
上面的東西涵蓋了提升的一些基本知識(shí),它們看起來也沒有那么迷惑。但是,在一些特殊場(chǎng)景,還是有一定的復(fù)雜度的。
變量解析順序
最需要牢記在心的是變量解析順序。記得我前面給出的命名進(jìn)入作用域的4種方式嗎?變量解析的順序就是我列出來的順序。
<script>
var a;
function a(){
}
alert(a);//打印出a的函數(shù)體
</script>
//但是要注意區(qū)分和下面兩個(gè)寫法的區(qū)別:
<script>
var a=1;
function a(){
}
alert(a);//打印出1
</script>
<script>
function a(){
}
var a=1;
alert(a);//打印出1
</script>
1 內(nèi)置的名稱arguments表現(xiàn)得很奇怪,他看起來應(yīng)該是聲明在函數(shù)形式參數(shù)之后,但是卻在函數(shù)聲明之前。這是說,如果形參里面有arguments,它會(huì)比內(nèi)置的那個(gè)有優(yōu)先級(jí)。這是很不好的特性,所以要杜絕在形參里面使用arguments;
2 在任何地方定義this變量都會(huì)出語法錯(cuò)誤,這是個(gè)好特性;
3 如果多個(gè)形式參數(shù)擁有相同的名稱,最后的那個(gè)具有優(yōu)先級(jí),即便實(shí)際運(yùn)行的時(shí)候它的值是undefined;
命名函數(shù)
你可以給一個(gè)函數(shù)一個(gè)名字。如果這樣的話,它就不是一個(gè)函數(shù)聲明,同時(shí),函數(shù)體定義里面的指定的函數(shù)名( 如果有的話,如下面的spam, 譯者注)將不會(huì)被提升, 而是被忽略。這里一些代碼幫助你理解:
var foo = function () {}; // foo指向匿名函數(shù)
function bar() {}; // 函數(shù)聲明
var baz = function spam() {}; // 命名函數(shù),只有baz被提升,spam不會(huì)被提升。
foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"
現(xiàn)在你理解了作用域和變量提升,那么這對(duì)于javascript編碼意味著什么?最重要的一點(diǎn)是,總是用var定義你的變量。而且我強(qiáng)烈推薦,對(duì)于一個(gè)名稱,在一個(gè)作用域里面永遠(yuǎn)只有一次var聲明。如果你這么做,你就不會(huì)遇到作用域和變量提升問題。
語言規(guī)范怎么說
我發(fā)現(xiàn)ECMAScript參考文檔總是很有用。下面是我找到的關(guān)于作用域和變量提升的部分:
如果變量在函數(shù)體類聲明,則它是函數(shù)作用域。否則,它是全局作用域(作為global的屬性)。變量將會(huì)在執(zhí)行進(jìn)入作用域的時(shí)候被創(chuàng)建。塊不會(huì)定義新的作用域,只有函數(shù)聲明和程序(譯者以為,就是全局性質(zhì)的代碼執(zhí)行)才會(huì)創(chuàng)造新的作用域。變量在創(chuàng)建的時(shí)候會(huì)被初始化為undefined。如果變量聲明語句里面帶有賦值操作,則賦值操作只有被執(zhí)行到的時(shí)候才會(huì)發(fā)生,而不是創(chuàng)建的時(shí)候。
我期待這篇文章會(huì)對(duì)那些對(duì)javascript比較迷惑的程序員帶來一絲光明。我自己也盡最大的可能去避免帶來更多的迷惑。如果我說錯(cuò)了什么,或者忽略了什么,請(qǐng)告知。
譯者補(bǔ)充
有位朋友提醒了我發(fā)現(xiàn)了IE下全局作用域下命名函數(shù)的提升問題:
我翻譯文章的時(shí)候是這么測(cè)試的:
這個(gè)問題還引導(dǎo)我思考了另2個(gè)問題,1:對(duì)于全局作用于范圍的變量,var與不var是有區(qū)別的. 沒有var的寫法,其變量不會(huì)被提升。比如下面兩個(gè)程序,第二個(gè)會(huì)報(bào)錯(cuò):
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注