一、引入
在了解這個知識點之前,我們先來看看下面的代碼,控制台都會輸出什么
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