深刻解析JS變量聲明和函數聲明提高

不少時候,在直覺上,咱們都會認爲JS代碼在執行時都是自上而下一行一行執行的,可是實際上,有一種狀況會致使這個假設是錯誤的。css

a = 2;
var a;
console.log(a);
複製代碼

按照傳統眼光,console.log(a)輸出的應該是undefined,由於var a在a = 2以後。可是,輸出的是2。html

再看第二段代碼:前端

console.log(a);
var a = 2;
複製代碼

有人會想到第一段代碼,而後回答undefined。還有人會認爲a在使用前未被聲明,所以拋出ReferenceError異常。遺憾的是,結果是undefined。vue

爲何呢?node

從編譯器的角度看問題webpack

JS在編譯階段,編譯器的一部分工做就是找到全部聲明,並用合適的做用域將他們關聯起來。對於通常人來講var a = 2僅僅是一個聲明,可是,JS編譯器會將該段代碼拆爲兩段,即:var a和a = 2。var a這個定義聲明會在編譯階段執行,而a = 2這個賦值聲明會在原地等待傳統意義上的從上到下的執行。web

因此,在編譯器的角度來看,第一段代碼其實是這樣的:面試

var a;  // 編譯階段執行
a = 2;
console.log(a);
複製代碼

因此,輸出的是2。bash

相似的,第二個代碼片斷其實是這樣執行的:函數

var a;
console.log(a);
a = 2;
複製代碼

這樣的話,很明顯,輸出的應該是undefined,由於只對a進行了定義聲明,沒有對a進行賦值聲明。

從上面這兩個例子能夠看出,變量聲明會從它們在代碼中出現的位置被移動到當前做用域的最上方進行執行,這個過程叫作提高。

函數提高

下面,再來看一段代碼

foo();

function foo () {
    console.log(a);
    var a = 2;
}
在這個例子中,輸出undefined而不會報錯,由於,函數變量也能提高。即,實際上像以下的狀況運行。

function foo () {
    var a;
    console.log(a);
    a = 2;//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
}

foo();
複製代碼

說到這裏,你是否是認爲提高很簡單,只要把變量都放到當前做用域最上方執行就行了?

下面,我來講一種意外狀況:函數表達式的提高狀況。

函數表達式的提高狀況

foo();

var foo = function bar () {
    console.log(a);
    var a = 2;
}
複製代碼

你是否是想說,這個例子不是和以前的那個差很少嗎?輸出的固然是undefined呀。可是,結果是,不輸出,由於JS報了TypeError錯誤!

由於,函數表達式不會進行提高!

該例子的實際運行狀況是這樣的:

var foo;
foo();
foo = function bar () {
    var a;
    console.log(a);
    a = 2;
}
複製代碼

因爲執行時,在做用域中找獲得foo(該做用域最上方聲明瞭foo),因此不會報ReferenceError錯誤,可是,foo此時沒有進行賦值(若是foo是一個函數聲明而不是函數表達式,那麼就會賦值),也就是說實際上foo()是對一個值爲undefined的變量進行函數調用,因此,理所應當拋出TypeError異常。

值得一提的是,即便是具名的函數表達式,名稱標識符在賦值以前也沒法在所在做用域中使用,即:

foo();  // TypeError
bar();  // ReferenceError

var foo = function bar () {}
複製代碼

函數優先

函數聲明和變量聲明都會被提高,可是有一個值得注意的細節,那就是,函數會首先提高,而後纔是變量!

看下面這一段代碼:

foo();
var foo;
function foo () {
    console.log(1);
}
foo = function () {
    console.log(2);
}
複製代碼

這一段代碼會輸出1,緣由就在於,函數優先。

這一段代碼能夠轉換爲如下形式:

function foo () {
    console.log(1);
}
var foo;    // 重複聲明,被忽略
foo();      // 輸出1
foo = function () {
    console.log(2);
}
複製代碼

若是,在代碼的結尾再執行一次foo函數,此時,輸出的是1。

function foo () {
    console.log(1);
}
var foo;    // 重複聲明,被忽略
foo();      // 輸出1
foo = function () {
    console.log(2);
}
foo();      // 輸出2
複製代碼

由於,儘管重複的聲明會被忽略了,可是後面的函數仍是能夠覆蓋前面的函數。

明白了這個道理,你就能夠理解下面這個問題了:

foo();
var a = true;
if (a) {
    function foo () {
        console.log("a");
    }
} else {
    function foo () {
        console.log("b");//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
    }//面向1-3年前端人員
}//幫助突破技術瓶頸,提高思惟能力

複製代碼

你猜這道題輸出的結果是什麼?是b!爲何?由於foo進行了兩次的聲明,可是,後一次函數覆蓋了前一次的函數。因此調用foo時,永遠調用的都是console.log("b")。

總結

1.全部聲明(變量和函數)都會被移動到各自做用域的最頂端,這個過程被稱爲提高

2.函數表達式等各類賦值操做並不會被提高

3.函數優先原則

4.儘可能避免產生提高問題

本次給你們推薦一個免費的學習羣,裏面歸納移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。 對web開發技術感興趣的同窗,歡迎加入Q羣:864305860,無論你是小白仍是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時天天更新視頻資料。 最後,祝你們早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峯。

相關文章
相關標籤/搜索