1. 程式人生 > >js變數提升 JS中var、let、const區別?

js變數提升 JS中var、let、const區別?

對於大多數js開發者來說,變數提升可以說是一個非常常見的問題,但是可能很多人對其不是特別的瞭解。所以在此,我想來講一講。

先從一個簡單的例子來入門:

a = 2;
var a;
​
console.log(a);

你覺得以上的程式碼會輸出什麼?是輸出undefined嗎?如果是按照程式的自上而下執行的話,那麼這一段程式碼確實是輸出undefined,因為var a語句把a = 2的語句給覆蓋了。然而,javascript並不是嚴格的自上而下執行的語言

這一段程式碼的輸出結果是2,是不是感到很意外?為什麼會這樣呢?這個問題的關鍵就在於變數提升(hoisting)。它會將當前作用域的所有變數的宣告提升到程式的頂部

,因此上面的程式碼其實等價於以下程式碼。這樣是不是就很簡單明瞭了。

var a;
a = 2;
​
console.log(a);

那麼接下來,我們再來看這個例子。

console.log(a);
​
var a = 2;

你覺得以上的程式碼會輸出什麼?是直接報ReferenceError嗎?還是輸出2呢?

其實以上程式碼會輸出undefined。為什麼呢?我們之前說過,js會將變數的宣告提升到頂部,可是賦值語句並不會提升。所以對於js來說,其實var a = 2是分為兩步的:

  1. var a;
  2. a = 2;

而js只會將第一步提升到頂部,所以上面的語句等價於:

var a;
​
console.log(a);
​
a = 2;

為什麼有變數提升

那麼為什麼會出現變數提升這個現象呢?

js和其他語言一樣,都要經歷編譯和執行階段。而js在編譯階段的時候,會蒐集所有的變數宣告並且提前宣告變數,而不會改變其他語句的順序,因此,在編譯階段的時候,第一步就已經執行了,而第二步則是在執行階段執行到該語句的時候才執行。

變數宣告

js的變數宣告其實大體上可以分為三種:var宣告、let與const宣告和函式宣告。

函式宣告與其他宣告一起出現的時候,就可能會引起一些困擾。我們來看下面的例子。

foo();
​
function foo() {
    console.log('foo');
}
var foo = 2;

你覺得上面會輸出什麼?TypeError嗎?還是輸出foo呢?想一想再接著往下看。

噹噹噹當,其實最後輸出的結果是foo。這就引出了我們的問題了,當函式宣告與其他宣告一起出現的時候,是以誰為準呢?答案就是,函式宣告高於一切,畢竟函式是js的第一公民。

那麼,下面的例子呢?

foo();
​
function foo() {
    console.log('1');
}
​
function foo() {
    console.log('2');
}

當出現多個函式宣告,那怎麼辦呢?

上面這個程式碼輸出結果為2。因為有多個函式宣告的時候,是由最後面的函式宣告來替代前面的。

想必經歷了以上的例子,你應該已經對變數宣告已經有一定的瞭解了。那麼我再來出一道題目來測試下。

foo();
​
var foo = function() {
    console.log('foo');
}

這道題目是不是非常簡單啊?這道題和上面的第二道例子其實是一樣的。var foo = function() {}這種格式我們叫做函式表示式。

它其實也是分為兩部分,一部分是var foo,而一部分是foo = function() {},參照例2,我們可以知道,這道題的結果應該是報了TypeError(因為foo宣告但未賦值,因此foo是undefined)。

如果函式宣告和變數宣告同名,會發生什麼?

再來看下面這個例子:

foo();
function foo() {
    console.log('foo');
}
var foo = 2;


// 執行結果為: foo

其實上面的執行過程  可以理解為:

function foo() {
    console.log('foo');
}
var foo;
foo();
foo = 2;


// 執行結果為: foo

那麼怎麼驗證是不是上面的過程呢? 看下面證明

function foo() {
    console.log('foo');
}
var foo;
console.log(typeof(foo));		// function
foo();
console.log(typeof(foo));		// function
foo = 2;
console.log(typeof(foo));		// number
foo();
console.log(typeof(foo));		// Uncaught TypeError: foo is not a function

即:函式宣告提升 高於 變數宣告提升,在同時聲明瞭同名的函式和變數 且 變數未被賦值時,此名(foo)仍然指向函式

講講let和const宣告

上面我們提到了var宣告,函式宣告,那麼接下來我們來講講let和const宣告。

let和const其實也是有函式提升的概念,不過它們會比較特殊,與var不一樣,它們存在一個臨死性死區的概念。我們可以通過一個例子來體現這一點。

var a = 2;
function test() {
    console.log(a);
    let a = 5;
}
test();

你覺得上面的程式碼會輸出什麼呢?是輸出2,還是undefined呢?

如果說,let沒有變數提升的效果的話,那麼應該是輸出2。如果說let擁有和var一樣的變數提升效果的話,那麼應該是輸出undefined。然而,其實上面的程式碼是會報錯的。會報"ReferenceError: a is not defined"錯誤。那麼為什麼呢?

這其實就是我說的,let雖然具有變數提升的功能,但是它又與var不一樣,它具有一個臨死性死區的概念。

臨死性死區其實就是說,a我已經聲明瞭,可是在沒有到它賦值的時候,你都不能使用這個變數,不然就會報錯。所以該當前作用域開始,一直到let a = 5為止這整一塊,都是a變數的臨死性死區,你不能使用它。

而const和let它們的變數提升的效果是一樣的,也都存在著臨死性死區的概念。

總結

那麼接下來我們來總結一下。

  1. js會將變數的宣告提升到js頂部執行,因此對於這種語句:var a = 2;其實上js會將其分為var a;和a = 2;兩部分,並且將var a這一步提升到頂部執行。
  2. 變數提升的本質其實是由於js引擎在編譯的時候,就將所有的變數聲明瞭,因此在執行的時候,所有的變數都已經完成宣告。
  3. 當有多個同名變數宣告的時候,函式宣告會覆蓋其他的宣告。如果有多個函式宣告,則是由最後的一個函式宣告覆蓋之前所有的宣告。
  4. let和const都具有變數提升的效果,但是它們都具有臨死性死區,從作用域開始部門,一直到變數的宣告語句這整一塊,你都不能使用該變數。

使用var宣告的變數,其作用域為該語句所在的函式內,且存在變數提升現象; 使用let宣告的變數,其作用域為該語句所在的程式碼塊內,不存在變數提升; 使用const宣告的是常量,在後面出現的程式碼中不能再修改該常量的值。

以上,就是本文的內容。覺得有用的話麻煩點個贊,嘻嘻。

作者:陳紀庚 連結:https://zhuanlan.zhihu.com/p/43159665 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。