本文同時發表在另一獨立部落格上http://qingbob.com/blog/%E8%B0%88javascript%E5%8F%98%E9%87%8F%E5%A3%B0%E6%98%8E
這篇文章還是對基礎的複習,對面試經曆的一個總結。
之前的面試中遇到過一道面試題
var a =10;
(function(){ console.log(a);
var a =20;}
)()
- 短短5行代碼log的結果是什嗎?
- 如果把
var a = 20;
和console.log(a)
語句順序對調呢?
這道題目的答案是undefined
。不是10。
關鍵在於javascript的變數聲明有一個hoisting機制,變數聲明永遠都會被提升至範圍的最頂端(注意測試還只是聲明,還沒有賦值)。其實上面的語句相當於:
var a =10;
(function(){
var a;//在這裡對變數hoisting,先聲明
console.log(a); a =20;//再賦值
})()
再精簡一點:
bla =2
var bla;
// 這是分割線,上下代碼的效果其實是一樣的
var bla;bla =2;
也就是先使用,再聲明(注意是聲明,還沒有賦值),這樣一來,聲明和賦值就被分開來了。所以最佳實務都推薦最好在函數的頂端把需要使用的變數首先聲明一遍。
同理,我們可以理解下面的代碼也是會報錯的
f()//明顯這裡有錯,因為f還沒有被賦一個函數
var f =function(){ console.log("Hello");
}
但有一個問題,如果將上例f的函式宣告修改一下,還會報錯嗎
f()//可以運行嗎?
function f(){ console.log("Hello");
}
這裡我其實想強調的是兩種函式宣告的var f = function () {}
和function f() {}
差別。
事實上,javascript中所有的函式宣告(function declarations)和變數聲明(variable declarations)都會被提升(hoisted)至它們所在範圍的最頂端。需要注意的是函式宣告只有一種,也就是function f() {}
的形式。而var f = function () {}
是什嗎?你可以理解為它是將一個匿名函數(當然也可以取函數名,下面會解釋)賦值給了一個變數。
就哪上面兩個例子來說,同樣是想實現先使用再定義的效果。只有第二種有用,雖然函數f在使用之後才定義,但是在javascript解譯器中,它仍然是先於執行語句被定義的。
而第一個例子,執行的效果是這樣的
var f;f()//沒有定義任何函數,當然無法執行f =function(){ console.log("Hello");
}
這麼看來,雖然javascript是允許先執行再聲明,但切勿這麼做,請遵循先聲明再使用的好習慣。
再看看另一種情況,如果我把之前的函數定義
var f =function(){};
- 給右側的匿名函數增加函數名
- 以右側函數名來執行函數
- 能成功嗎?
var f =function ab(){};ab();
答案是否定的,因為上面的代碼對f函數的定義是以命名函數運算式(Named Function Expressions),而並非真正的函式宣告,注意該函數名只在該函數的範圍內有用。下面這段代碼充分說明了它的意義:
var f =function foo(){
return typeof foo;
};
typeof foo;// "undefined"f();// "function"
那麼如此聲明還有什麼意義呢?好吧,就我目前找到的資料而言,這樣做的好處就是便於調試。
接下來考慮一些意想不到的邊緣,雖然我覺得一個程式員寫出下面的代碼有點自找苦吃,而且應該是在實戰中避免的,但作為考試的題目來說是值得一說的。比如對比下面兩段代碼:
function value(){
return 1;
}
var value;alert(typeof value);//"function"
function value(){
return 1;
}
var value =1;alert(typeof value);//"number"
第一段代碼想說明的是函式宣告會覆蓋變數聲明,注意是聲明,還沒有賦值。如代碼中,雖然同名變數在函數後再次聲明,但是typeof的結果仍然是function
第二段代碼想說明的是函式宣告不會覆蓋變數賦值或者說初始化,如代碼所示
Name Resolution Order
為什麼會有上面的結果,為什麼函數的聲明會覆蓋變數的聲明。就是因為name resolution order。我不知道怎麼翻譯這個名詞,暫且就翻譯為名稱解析順序吧。
在javascript中,一個變數名(name)有四種方式進入範圍(scope)中
- 語言內建,所有的範圍中都有
this
和arguments
關鍵字
- 形式參數,函數的參數在整個範圍中都是有效
- 函式宣告
- 變數聲明
上面列出的四種順序也正是由高到底的優先順序的順序(關於這點我有所保留,我測試的結果是參數和函數的優先順序都會比語言內建的優先順序高,你可以把形式參數取名為arguments,或者定義一個函數名為arguments,結果內建的argument說被覆蓋了),一旦一個變數名已經聲明了,那麼它就不可能被其他更低優先順序的變數聲明形式所覆蓋。
參考文章:
- Named function expressions demystified
- function-declarations-vs-function-expressions
- JavaScript Scoping and Hoisting
- Answering Baranovskiy’s JavaScript quiz