一、引入
在瞭解這個知識點之前,我們先來看看下面的程式碼,控制檯都會輸出什麼
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();
小白理解:foo是一個全域性變數,值為1,當執行bar函式的時候,對1取反的結果是false,不會執行bar函式內部的if語句,所以彈出1
小爐:不不不,你並不知道變數提升和函式提升,請看下面正確的程式碼執行過程
var foo;
foo = 1 function bar(){ var foo; if(!foo){ foo = 10; } alert(foo); } bar();
上面這段程式碼才是正確的執行順序,在呼叫bar函式時,內部的foo產生了變數提升,提升到函式內部的頂端,又因為只是宣告變數foo並未賦值,所以foo的值為undefined,取反則為true,然後if語句執行,將foo的值改為10,最後alert彈出10。這是為什麼上面程式碼不輸出1而輸出10的原因,那麼下面我們來介紹相關概念
二、從js預解析過程理解函式提升和變數提升
1.預解析過程
(1)把變數的宣告提升到當前作用域的最前面,只會提升宣告,不會提升賦值。
(2)把函式的宣告提升到當前作用域的最前面,只會提升宣告,不會提升呼叫。
(3)先提升var,再提升function。(只有函式和變數同名時才是函式優先)
(4)提升完後其他程式碼位置不變
舉個例子
例1:
var a = 25;
function abc() {
alert(a);
var a = 10;
}
abc();
預解析後如下:
var a;
function abc() {
var a;
alert(a);
a = 10;
}
a = 25;
abc(); //ndefined;
從字面上理解就是變數和函式的宣告會移動到移動到函式或者全域性程式碼的開頭位置,先提升var,再提升function,提升完成後不改變其他程式碼位置
2.函式提升
函式提升,只會提升函式宣告,而不會提升函式表示式。
f();
fn();
// 函式表示式
var fn = function(){
console.log(1);
}
// 函式宣告
function f(){
console.log(0);
}
真實的執行過程是怎麼樣的呢?看下面
// 函式宣告
function f(){
console.log(0)
}
var fn;
f();
fn();
fn = function(){
console.log(1);
}
所以在fn()時就會報錯,說fn不是一個函式。也證明了函式提升,只會提升函式宣告,而不會提升函式表示式。
3.變數提升
console.log(a) // undefined
var a = 'hello JS'
/* 在我們宣告a之前為什麼輸出a不會報錯呢? 不急,讓我們接著往下看 */
num = 6;
num++;
var num;
console.log(num) // 7 為什麼給一個還沒有宣告的變數賦值會不報錯呢
上面的程式碼能夠正確執行的原因都是因為變數提升,將變數提升到當前作用域的最前面,由於並未賦值,所以上面第一個console會打出undefined
三、同名函式和變數提升時怎麼辦?
1.函式和變數同名
舉個例子
var a = 1;
function a(){
console.log(a)
}
console.log(a)
上面的程式碼最終結果是列印出1,同名的函式宣告和變數宣告,採用的是忽略原則,由於在提升時函式宣告會提升到變數宣告之前,變數宣告一定會被忽略,所以結果是函式宣告有效。但是賦值語句不會消失,所以賦值為1。
2.函式同名
舉個例子
fn();//2
function fn() { console.log(1); }
fn();//2
var fn = 10;
fn();//報錯 fn is not a function
function fn() { console.log(2); }
fn();
函式同名時,後面宣告的函式會覆蓋前面的函式
3.變數同名
(1)全域性變數和函式內部變數同名
var a = 1;
function test(x) {
alert(x);//undefined
alert(a);//undefined
var a = 2;
alert(a);//2
}
test();
程式碼執行過程:首先宣告變數a和test函式,然後給a賦值為1,呼叫函式test,因為沒有給test傳參,x為undefined,又因為函式內部有var宣告的變數a,存在變數提升,所以先彈出a為undefined,再給a賦值為2,再彈出2。
我們可以看到,區域性變數的優先順序高於同名的全域性變數 。如果在函式中宣告一個區域性變數同名,則全域性變數就會被區域性變數覆蓋。
(2)同一環境下的兩個同名變數,後一個會覆蓋前一個
四、由函式提升和變數提升引出的作用域問題
1.javascript是沒有塊級作用域的,函式是javascript中唯一擁有自身作用域的結構
2.宣告變數,實際上就是定義了一個名字,在記憶體中開闢了儲存空間,並且初始為undefined,提升到當前作用域頂部,只提升變數,不提升所賦的值
3.塊內的變數宣告和函式宣告也會被提升,例如if語句。
4.區域性變數的優先順序高於同名的全域性變數,當在自身作用域內找不到該變數的時候,會沿著作用域鏈逐步向上查詢,若在全域性作用域內部仍找不到該變數,則會丟擲異常。
五、如何取消函式提升和變數提升
ES6中引入了塊級作用域的概念,也有let、const、class等新的宣告方式來避免函式提升和變數提升帶來的問題(ES6語法傳送門)
六、總結
1.函式宣告和變數宣告會被提升到作用域的頂部,只提升宣告,提升完成後其他程式碼位置不改變
2.同名的函式宣告和變數宣告,採用的是忽略原則,由於在提升時函式宣告會提升到變數宣告之前,變數宣告一定會被忽略,所以結果是函式宣告有效
3.同名變數、同名函式後宣告的會覆蓋前面的
4.塊內的變數宣告和函式宣告也會被提升,例如if語句。
5.函式宣告和函式表示式相比,函式宣告使用可以更加自由,可以放在隨意的位置,因為它能夠整體的變數提升。而函式表示式使用就相對沒有那麼自由了,呼叫必須在宣告的後面,因為變數提前只是將表示式的變數提前,並沒有將表示式的內容提前。
6.區域性變數的優先順序高於同名的全域性變數
參考文件:https://www.cnblogs.com/nangezi/p/9105778.html
https://blog.csdn.net/liuqiao0327/article/details/106971270/
https://juejin.im/post/6844904179371081741#heading-0