js變數提升與函式提升的過程
js變數提升與函式提升的詳細過程
先來看兩個栗子,下面的兩段程式碼分別輸出什麼?
// 程式碼段1
function foo() {
var a = 1;
function a() {}
console.log(a);
}
foo();
// 程式碼段2
function foo() {
var a;
function a() {}
console.log(a);
}
foo();
答案是:程式碼段1列印的是1,程式碼段2列印的是 a() 函式。
為什麼會這樣呢?這就涉及到js中的變數提升和函式提升的具體過程了。
1、變數的提升
js是怎麼建立變數的呢?
如下面的程式碼:
var a = 1;
var b = 2;
js在解析上面的程式碼的時候,其實會按照下面的方式進行解析的:
var a;
var b;
a = 1;
b = 2;
所以 js 並不是在我們定義一個變數的時候,宣告完成之後立即賦值,而是把所有用到的變數全部宣告之後,再到變數的定義的地方進行賦值,變數的宣告的過程就是變數的提升。
所以我們看下下面的栗子:
function foo() {
var a = 1;
console.log(a);
console.log(b);
var b = 2;
}
foo();
上面的程式碼在js的眼中是這樣解析的:
function foo() { var a; var b; a = 1; console.log(a); // 1 console.log(b); // undefined b = 2; } foo();
所以輸出的 a 的值為1, b的值為 undefined。
變數在宣告提升的時候,是全部提升到作用域的最前面,一個接著一個的。但是在變數賦值的時候就不是一個接著一個賦值了,而是賦值的位置在變數原本定義的位置。原本js定義變數的地方,在js執行到這裡的時候,才會進行賦值操作,而沒有執行到的變數,不會進行賦值操作。
所以變數的提升,提升的其實是變數的宣告,而不是變數的賦值。
2、函式的提升
函式的提升和變數的提升類似,都是提升到作用域的最開始的位置,只不過變數的提升是分兩步的,第一步是變數宣告的提升,第二步是變數的賦值。而函式的提升是直接將整個函式整體提升到作用域的最開始位置,相當於剪下過去的樣子。
3、變數提升和函式提升的順序#
在作用域中,不管是變數還是函式,都會提升到作用域最開始的位置,不同的是,函式的提升後的位置是在變數提升後的位置之後的。
舉個栗子:
下面的程式碼輸出什麼?
function foo() {
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
}
foo();
上面的程式碼在js眼中是這樣解析的:
function foo() {
var a;
function a() {}
console.log(a); // a()
a = 1;
console.log(a); // 1
console.log(a); // 1
}
foo();
所以從上面的栗子可以看到,變數的提升是在函式提升之前的,但是變數賦值的部分是在js原型到變數定義的位置才給變數賦值的,而函式提升是相當於直接剪下到最前面的。
我們再看一個更加複雜一點的栗子:
function foo() {
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
console.log(b);
var b = 2;
console.log(b);
function b() {}
console.log(b);
}
foo();
js是這樣解析的:
function foo() {
var a;
var b;
function a() {}
function b() {}
console.log(a); // a()
a = 1;
console.log(a); // 1
console.log(a); // 1
console.log(b); // b()
b = 2;
console.log(b); // 2
console.log(b);// 2
}
foo();
最後,注意:只有宣告的變數和函式才會進行提升,隱式全域性變數不會提升。
下面的栗子中,b不會進行變數提升。
function foo() {
console.log(a);
console.log(b); // 報錯
b = 'aaa';
var a = 'bbb';
console.log(a);
console.log(b);
}
foo();