原文連接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個關鍵字來聲明一個變量:let
和const
。spa
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命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。
提高(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 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); // ?
第二題的解析請看 這裏