聲明提前,函數聲明提前,好吧,老生常談的問題了。正好,前些天在掘金看到一道關於聲明提前的筆試題,那么這里就以這道題來作為本文的引子吧,代碼如下:
console.log(a)//? a();//? var a =3; function a(){ console.log(10); } console.log(a);//? a = 6; a();//?
四處分別輸出什么?為什么?讀完本文,最少也能在你心中激起一絲波瀾了。
壹 ❀ 什么是聲明提前
先來了解一個函數作用域的概念:變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內始終可見。說直白點,在聲明一個變量的前后,你都可以直接使用它,並不會報錯。舉個例子:
(function(){ console.log(a);//undefined var a ="小鑽風"; console.log(a);//小鑽風 }())
前面已經說了,變量在聲明它們的函數體內始終可見,盡管第一個console輸出在聲明a之前,但它依舊能輸出,並不會報錯,那是因為聲明統一提前,賦值原地不變。上面代碼等同於:
(function(){ var a; console.log(a);//聲明了但未賦值,所以輸出undefined; a ="小鑽風"; console.log(a);//上一步賦值了,所以輸出小鑽風 }())
聲明提前了,只是沒有賦值,賦值仍保留遠處不變,所以說變量a在function每一處都是可用的,就是這么個怪邏輯。
貳 ❀ 什么是函數聲明提前(函數體提前)
函數聲明提前的原理與變量聲明提前情況類似,需要提醒的是,只有函數聲明格式的函數才會存在函數聲明提前,比如函數表達式,構造函數,都不存在函數聲明提前。
函數創建的三種寫法:
a.函數聲明:function fun(a){console.log(a)};(只有這個家伙存在函數聲明提前)
b.函數表達式:var fun = function(a){console.log(a)};
c.構造函數:var fun = new Function("a",console.log(a));
直接上個例子:
num()//1 console.log(num)//函數本身 function num(){ console.log(1); } num();//1 console.log(num)//函數本身
有疑問的應該就是函數之前的函數調用與console了,前面說過了,函數聲明的情況與變量聲明類似,你可以理解為,在同一作用域內函數聲明后,此函數會跑到本作用域的最前面。上面的代碼等同於:
function num(){ console.log(1); } num()//1 console.log(num)//函數本身 num();//1 console.log(num)//函數本身
那么再來看看函數表達式是否會函數體提前:
num()//報錯 console.log(num)//undefined var num = function (){ console.log(1); } num();//1 console.log(num)//函數本身
第一個num()就會報錯,后面三個是看不到輸出的,這里是假設不受num()報錯影響本應輸出的情況。為什么會這樣呢,還記得前面變量聲明提前的原理嗎,這里只是將后面的普通賦值換成了函數,所以以上代碼等同於:
var num; num()//報錯,這時候都沒有函數聲明 console.log(num)//undefined,因為已經聲明了num num = function (){ console.log(1); } num();//1,有函數了啊,可以調用了 console.log(num)//函數本身,有函數了。
聲明提前,賦值不變,前面只聲明了num,並不存在函數,又怎么能調用num函數呢,所以第一個就報錯了,這里總該明白了吧。
叄 ❀ 變量聲明提前,函數聲明提前順序
這里就有個問題了,函數聲明提前,變量聲明也提前,到底誰會更提的更前?假設兩者都用的同一命名聲明,到底最后會輸出啥,我們來看個例子:
console.log(a);
var a = "孫悟空";
function a(){ console.log("小鑽風"); }
照理說,函數先提前,然后變量a在提前,a未賦值,覆蓋了上面聲明的函數a,應該輸出undefined,但為什么輸出的還是函數本體?
引入一個概念,你不知道的JavaScript(上卷)一書的第40頁中寫到:函數會首先被提升,然后才是變量。也就是說,同一作用域下提前,函數會在更前面。以上代碼等同於:
function a(){ console.log("小鑽風"); } var a;//由於上面函數已聲明a,相同的變量名聲明會被直接忽略
console.log(a);//輸出函數本體
a = "孫悟空";
為啥函數提前之后又var a;了怎么不輸出undefined,因為這里只是再次聲明a,並未修改現有a的值,做個簡單測試就可以了:
var a=1; var a; console.log(a);//1
變量a已經聲明過了,而且也賦值了,后面再次聲明只是聲明並未修改值,這種聲明方式會被直接忽略,所以還是輸出1.
這里討論了變量聲明提前,函數聲明提前以及提前先后順序,那么我們再回頭,改寫文章開頭的筆試題,那么它等同於:
正確的修改:
function a(){ console.log(10); } var a;//再次聲明a,並未修改a的值,忽略此處聲明 console.log(a)//輸出函數本體 a();//函數聲明提前,可調用,輸出10 a =3;//這里修改值了,a=3,函數已不存在 console.log(a);//輸出3 a = 6;//再次修改為6,函數已不存在 a();//a已經為6,沒有函數所以沒法調用,直接報錯
錯誤的修改:
var a;//再次聲明a,並未修改a的值 function a(){ console.log(10); } console.log(a)//輸出函數本體 a();//函數聲明提前,調用輸出10 a =3;//這里修改值了,a=3,不在是函數了 console.log(a);//輸出3 a = 6;//再次修改為6 a();//a已經為6,不存在函數了,所以沒法調用,報錯
可能很多人的思路是,var a=3在前,函數聲明在后,var a先提前,然后函數再次提前覆蓋了var a;你們也能發現上面兩種改寫結果都是一樣的,因為我在上面的橙色解釋中說了,但其實它們的提前是有固定的先后順序的,這里希望大家能清楚。
那么本文的介紹就這里了,作為自己的筆記,也希望能對大家有所幫助。文章思路借鑒了一下博文以及問題,挺厲害的文章,大家也可以閱讀看看。
本文只是解釋了什么是變量提升,准確來說變量提升是執行上下文搞得鬼,代碼在執行前都會做一番准備工作,也就是創建執行上下文,如果大家對於變量提升是何原理有興趣,可以讀讀博主 一篇文章看懂JS執行上下文 這篇文章。對於你加深理解一定有所幫助。
肆 ❀ 參考
csdn中js中是函數聲明先提升還是變量先提升
那么就寫到這里了。