hoist
vt.升起,提起;
vi.被舉起或擡高;
n.起重機,升降機; 升起; <俚>推,託,舉;javascript
原文地址:http://www.bootcss.com/article/variable-and-function-hoisting-in-javascript/css
這篇文章寫的真不錯,一看就明白了,先收藏!java
這篇文章不講英語,可是對於某些英語單詞找不到很好的翻譯,一上來就列出「hoist」這個單詞的釋義是爲了讓你們有個準備,我在這裏將此單詞翻譯爲「提早」,是爲了解釋 JavaScript 語言中很「古怪」的一個特性。編程
JavaScript 的語法和 C 、Java、C# 相似,統稱爲 C 類語法。有過 C 或 Java 編程經驗的同窗應該對「先聲明、後使用」的規則很熟悉,若是使用未經聲明的變量或函數,在編譯階段就會報錯。然而,JavaScript 卻可以在變量和函數被聲明以前使用它們。下面咱們就深刻了解一下其中的玄機。函數
先來看一段代碼:編碼
(function() { //ReferenceError: noSuchVariable is not defined console.log(noSuchVariable); })();
運行上面代碼立馬就報錯,不過,這也正是咱們指望的,由於 noSuchVariable
變量根本就沒有定義過嘛!再來看看下面的代碼:spa
(function() { // Outputs: undefined console.log(declaredLater); var declaredLater = "Now it's defined!"; // Outputs: "Now it's defined!" console.log(declaredLater); })();
首先,上面這段代碼是正確的,沒有任何問題。可是,爲何不報錯了?declaredLater
變量是在調用語句後面定義的啊?爲何竟然輸出的是 undefined
?翻譯
這實際上是 JavaScript 解析器搞的鬼,解析器將當前做用域內聲明的全部變量和函數都會放到做用域的開始處,可是,只有變量的聲明被提早到做用域的開始處了,而賦值操做被保留在原處。上述代碼對於解析器來講實際上是以下這個樣子滴:code
(function() { var declaredLater; //聲明被提早到做用域開始處了! // Outputs: undefined console.log(declaredLater); declaredLater = "Now it's defined!"; //賦值操做還在原地! // Outputs: "Now it's defined!" console.log(declaredLater); })();
這就是爲何上述代碼不報異常的緣由!變量和函數通過「被提早」以後,declaredLater
變量其實就被放在了調用函數的前面,根據 JavaScript 語法的定義,已聲明而未被賦值的變量會被自動賦值爲 undefined
,因此,第一次打印 declaredLater
變量的值就是 undefined
,後面咱們對declaredLater
變量進行了賦值操做,因此,第二次再打印變量就會輸出Now it's defined!
。ip
再來看一個例子:
var name = "Baggins"; (function () { // Outputs: "Original name was undefined" console.log("Original name was " + name); var name = "Underhill"; // Outputs: "New name is Underhill" console.log("New name is " + name); })();
上述代碼中,咱們先聲明瞭一個變量 name
,咱們的本意是但願在第一次打印 name
變量時可以輸出全局範圍內定義的 name
變量,而後再在函數中定義一個局部 name
變量覆蓋全局變量,最後輸出局部變量的值。但是第一次輸出的結果和咱們的預期徹底不一致,緣由就是咱們定義的局部變量在其做用域內被「提早」了,也就是變成了以下形式:
var name = "Baggins"; (function () { var name; //注意:name 變量被提早了! // Outputs: "Original name was undefined" console.log("Original name was " + name); name = "Underhill"; // Outputs: "New name is Underhill" console.log("New name is " + name); })();
因爲 JavaScript 具備這樣的「怪癖」,因此你會看到不少編碼指南建議你們將變量聲明放在做用域的最上方,這樣就能時刻提醒本身注意了。
前邊說的是變量,接下來咱們說說函數。
函數的「被提早」還要分兩種狀況,一種是函數聲明,第二種是函數做爲值賦值給變量。
先說第一種狀況,上代碼:
// Outputs: "Yes!" isItHoisted(); function isItHoisted() { console.log("Yes!"); }
如上所示,JavaScript 解釋器容許你在函數聲明以前使用,也就是說,函數聲明並不只僅是函數名「被提早」了,整個函數的定義也「被提早」了!因此上述代碼可以正確執行。
再來看第二種狀況:函數做爲值賦值給變量。(還記得嗎?在 JavaScript 中,函數也能夠做爲值賦予變量!)仍是先上代碼:
// Outputs: "Definition hoisted!" definitionHoisted(); // TypeError: undefined is not a function definitionNotHoisted(); function definitionHoisted() { console.log("Definition hoisted!"); } var definitionNotHoisted = function () { console.log("Definition not hoisted!"); };
咱們作了一個對比,definitionHoisted
函數被妥妥的執行了,符合第一種類型;definitionNotHoisted
變量「被提早」了,可是他的賦值(也就是函數)並無被提早,從這一點上來講,和前面咱們所講的變量「被提早」是徹底一致的,而且,因爲「被提早」的變量的默認值是 undefined
,因此報的錯誤屬於「類型不匹配」,由於 undefined
不是函數,固然不能被調用。
經過上面的講解能夠總結以下:
經過練習上面的實例本身多感覺一下。另外,做爲最佳實踐:變量聲明必定要放在做用域/函數的最上方(JavaScript 只有函數做用域!)。