javascript對變量和函數的聲明提早‘hoist’

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 只有函數做用域!)。

參考文獻

相關文章
相關標籤/搜索