JavaScript 變量聲明提高

JavaScript 變量聲明提高

原文連接javascript

一個小例子

先來看個例子:html

console.log(a);    // undefined
var a = 2;
console.log(a);    // 2

爲何是這樣的結果呢?這是由於 JavaScript 代碼在執行以前會有一個 預解析 階段,在這個階段,解釋器會將全部 變量聲明函數聲明 提高到他們各自的做用域頂部。java

注:變量聲明提高只是預解析階段的一部分行爲!express

若是變量在函數體內聲明,它的做用域是函數做用域(function-level scope)。不然,它就是全局做用域。segmentfault

繼續上面的例子,由於這個預解析階段,上面的代碼會被解釋器預解析成下面的代碼:函數

var a;
console.log(a);    // undefined
a = 2;
console.log(a);    // 2

變量的聲明

在 ES6 以前,一般經過 var 來聲明一個變量,可是 ES6 發佈後,又新添了2個關鍵字來聲明一個變量:letconstspa

  • var 聲明瞭一個變量,這個變量的做用域是當前執行位置的上下文:一個函數的內部(聲明在函數內)或者全局(聲明在函數外)code

  • let 聲明瞭一個塊級域的局部變量,而且它聲明的變量只在所在的代碼塊內有效htm

  • const 聲明瞭一個只讀的塊級域的常量,而且它聲明的常量也只在所在的代碼塊內有效blog

{ // 代碼塊
  var a = 1;
  let b = 2;
  const c = 3;
  
  console.log(a);   // 1
  console.log(b);   // 2
  console.log(c);   // 3
}

console.log(a);   // 1
console.log(b);   // 報錯,ReferenceError: b is not defined(…)
console.log(c);   // 未執行, 由於上面語句出錯,因此這條語句再也不執行,若是上面的語句不報錯,那麼這裏就會報錯
(function (){
  var a = 1;
  let b = 2;
  const c = 3;
  
  console.log(a);   // 1
  console.log(b);   // 2
  console.log(c);   // 3
})();   // 爲了方便,這裏使用了自執行函數

console.log(a);   // 報錯,ReferenceError: a is not defined(…)
console.log(b);   // 未執行
console.log(c);   // 未執行
  • let const 不像 var 那樣會發生「變量提高」現象。

console.log(a);    // 報錯,ReferenceError: a is not defined
let a = 2;
console.log(a);    // 待執行
console.log(a);    // 報錯,ReferenceError: a is not defined
const a = 2;
console.log(a);    // 待執行

ES6明確規定,若是區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。

變量聲明提高(Variable hoisting)

提高(hoisting)影響了變量的生命週期,一個變量的生命週期包含3個階段:

  • 聲明 - 建立一個新變量,例如 var myValue

  • 初始化 - 用一個值初始化變量 例如 myValue = 150

  • 使用 - 使用變量的值 例如 alert(myValue)

當代碼按照這三個步驟的順序執行的時候,一切看起來都很簡單,天然。

  • 變量提高的部分只是變量的聲明,賦值語句和可執行的代碼邏輯還保持在原地不動

console.log(a);     // undefined
var a = 111;
function fun(){
  console.log(a);   // undefined
  var a = 222;
  console.log(a);   // 222
}
fun();
console.log(a);     // 111

// --------------
//變量提高後

function fun(){
  var a;
  console.log(a);   // undefined
  a = 222;
  console.log(a);   // 222
}
var a;
console.log(a);     // undefined
a = 111;
fun();
console.log(a);     // 111
  • 在基本的語句(或者說代碼塊)中(好比:if語句for語句while語句switch語句for...in語句等),不存在變量聲明提高

var a = "aaa";
{
  console.log(a);       // aaa
  var a = "bbb";
}
console.log(a);         // bbb

//---------------

var a = "aaa";
if (true) {
  console.log(a);       // aaa
  var a = "bbb";
}
console.log(a);         // bbb

//---------------

var a = "aaa";
for(let x in window){
  console.log(a);       // aaa
  var a = "bbb";
  break;
}
console.log(a);         // bbb

函數聲明提高(Function Hoisting)

函數聲明(function declarations) 和 函數表達式(function expressions)在語法上實際上是等價的,可是有一點不一樣,就是 JavaScript 引擎 加載他們的方式不同。簡單講,就是函數聲明會被提高到其做用域頂部,而函數表達式不會。

更多細節

  • 函數聲明會提高,可是函數表達式的函數體就不會提高了

fun();       // hello 

function fun(){
  console.log("hello");
}

// --------------
// 提高後

function fun(){
  console.log("hello");
}

fun();       // hello
fun();       // 報錯,TypeError: fun is not a function

var fun = function(){
  console.log("hello");
};

// --------------
// 提高後

var fun;

fun();        // 報錯,TypeError: fun is not a function

fun = function(){
  console.log("hello");
};

當函數表達式的函數再也不是匿名函數,而是一個有函數名的函數時,會發生什麼?

foo();  // 報錯,TypeError "foo is not a function"
bar();  // 有效的
baz();  // 報錯,TypeError "baz is not a function"
spam(); // 報錯,ReferenceError "spam is not defined"

// anonymous function expression ('foo' gets hoisted)
var foo = function () {};     

// function declaration ('bar' and the function body get hoisted)
function bar() {}; 

// named function expression (only 'baz' gets hoisted)
var baz = function spam() {}; 

foo(); // 有效的
bar(); // 有效的
baz(); // 有效的
spam(); // 報錯,ReferenceError "spam is not defined"
  • 若是一個變量和函數同名,函數聲明優先於變量聲明(畢竟函數是 JavaScript 的第一等公民),而且與函數名同名的變量聲明將會被忽略。

fun();    // 輸出的結果爲111
function fun(){
  console.log(111);
}
var fun = function(){
  console.log(222);
}
fun();    // 輸出的結果爲222 

// --------------
// 提高後

function fun(){
  console.log(111);
}
fun();    // 輸出的結果爲111
fun = function(){       // 從新定義了變量 fun
  console.log(222);
}
fun();    // 輸出的結果爲222
  • 若是定義了相同的函數變量聲明,後定義的聲明會覆蓋掉先前的聲明,看以下代碼:

foo();    //輸出3
function foo(){
  console.log(1);
}
var foo = function(){
  console.log(2);
}  
function foo(){
  console.log(3);
}

練習

  • alert(foo) 的值是多少?

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo); // ?
}
bar();
  • alert(a) 的值是多少?

var a = 1;
function b() {
    a = 10;
    return;
    function a() {}
}
b();
alert(a);  // ?

第二題的解析請看 這裏

參考資料

相關文章
相關標籤/搜索