js和其他語言一樣嚷堡,都要經(jīng)歷編譯和執(zhí)行階段尸变。而js在編譯階段的時候义图,會搜集所有的變量聲明并且提前聲明變量,而其他的語句都不會改變他們的順序召烂,因此碱工,在編譯階段的時候,第一步就已經(jīng)執(zhí)行了奏夫,而第二步則是在執(zhí)行階段執(zhí)行到該語句的時候才執(zhí)行
也就是說怕篷,在編譯的時候,頁面上所有的變量都已經(jīng)被聲明了酗昼,但是都還沒有賦值廊谓,即數(shù)據(jù)類型暫為undefined。
例1
a = 5
var a
console.log(a)
那么這里會打印出什么呢麻削?按照正常的從上到下的邏輯思路蒸痹,第2行的a并沒有被定義舔示,所以第四行打印a時應(yīng)該會報錯說 a is not defined,但是电抚,注意注意,這里的a被提升了竖共。
“變量提升會將當(dāng)前作用域的所有變量的聲明提升到程序的頂部”蝙叛,意思就是說,雖然在第2行的a沒有用關(guān)鍵字 var 定義公给,但是編譯時會查找所有的var借帘,把它們放到該作用域的最頂部,相當(dāng)于以下的代碼執(zhí)行過程:
var a
a = 5
console.log(a)
例2
console.log(a)
a = 5
var a
是不是跟例1很像淌铐?只不過把賦值語句跟打印語句調(diào)換了一下位置肺然。那么現(xiàn)在第2行的執(zhí)行結(jié)果是打印出什么呢?結(jié)果是 undefined腿准〖势穑回憶一下變量提升的操作,把所有的var都提到最頂部吐葱,在這里就是把 var a 放到最開頭街望,接下來就是console.log(a),很明顯弟跑,這里的a還沒被賦值灾前,是在打印語句之后才被賦值,所以孟辑,已定義但未賦值的變量的數(shù)據(jù)類型是undefined哎甲,所以打印結(jié)果是undefined。
理解到簡單的定義之后饲嗽,就要思考一下相對比較復(fù)雜的變量提升了炭玫,我們結(jié)合函數(shù)來看看
第一種情況:函數(shù)有參數(shù),且與函數(shù)內(nèi)部的變量同名
function show(a){
console.log(a);//10
var a=20
console.log(a);//20
}
show(10)
開始學(xué)函數(shù)參數(shù)的時候貌虾,老師跟我們說础嫡,形參就相當(dāng)于是在函數(shù)內(nèi)部定義了一個變量,就是var 了一個變量酝惧;然后實參傳進來就相當(dāng)于是給這個變量賦了值榴鼎。接下來我們就可以沿著這個思路去分析。還有一點:參數(shù)名和變量名同名晚唇,參數(shù)優(yōu)先級高于變量提升
因為參數(shù)變量提升優(yōu)先級高巫财,所以在函數(shù)被調(diào)用后,參數(shù)傳值的執(zhí)行順序是在變量賦值之前的哩陕,即var a之后平项,是 a = 10(傳參)赫舒,相當(dāng)于以下的代碼執(zhí)行順序:
function show(){
var a
a = 10
console.log(a);//10
a = 20
console.log(a);//20
}
show()
第二種情況:函數(shù)沒有參數(shù),函數(shù)內(nèi)部的變量未以關(guān)鍵字 var 定義
function show(){
a=10 // 不加var闽瓢,此處的a就變成了全局變量
console.log(a); //10
}
show()
console.log(a); //10
注意看第2行的a接癌,它前面沒有 var 關(guān)鍵字,此時扣讼,當(dāng)show函數(shù)被調(diào)用后缺猛,a就被添加到了window對象中,成為了一個全局變量椭符,全局變量在script的任何地方都能被訪問荔燎,所以最后一行,在函數(shù)外部也能訪問 a销钝,結(jié)果打印10(如果前面沒有調(diào)用show有咨,即show(),那么a就沒被創(chuàng)建蒸健,即未定義)
第三種情況:函數(shù) 有/無 參數(shù)座享,函數(shù)內(nèi)部的變量以 var 定義
此時的var 后面的變量就是一個局部變量,也就是說似忧,這個變量只能在其作用于內(nèi)部被訪問征讲。
function show(){
var a=10
console.log(a); //10
}
show()
console.log(a);// a is not defined 局部變量 不能在外邊被訪問
第四種情況:函數(shù)外部的全局變量跟函數(shù)內(nèi)部的局部變量同名
var tt = 'aa';
function test(){
alert(tt); //先變量提升,tt在全局和函數(shù)內(nèi)部都有橡娄,優(yōu)先考慮局部變量提升诗箍,所以var tt提到alert上面,即undefined
var tt = 'dd'; //局部變量賦值挽唉,所以tt現(xiàn)在有值了滤祖,為"dd""
alert(tt); //彈出dd
}
test();
當(dāng)局部變量和全局變量都有提升時,優(yōu)先考慮局部變量提升瓶籽。
第五種情況:函數(shù)提升跟變量提升的變量重名
函數(shù)也是一個變量匠童,可以用函數(shù)聲明的方式定義,也可以用函數(shù)表達式的方式定義塑顺,所以也存在函數(shù)提升汤求。
當(dāng)有多個同名變量聲明的時候,函數(shù)聲明會覆蓋其他的聲明严拒。如果有多個函數(shù)聲明扬绪,則是由最后的一個函數(shù)聲明覆蓋之前所有的聲明。
舉個例子來解釋一下:
fn()
function fn() {
console.log('1')
var fn = 40
console.log(fn)
}
function fn() {
console.log('2') //2
var fn = 10
console.log(fn) //10
}
var fn = function(){
console.log('3')
}
console.log(fn) //function(){console.log('3')}
在這里裤唠,第一行就先調(diào)用了函數(shù)fn挤牛。因為出現(xiàn)變量重名的情況時,函數(shù)聲明高于所有變量聲明种蘸,即函數(shù)聲明最先實現(xiàn)墓赴,那么第3行和第9行的function fn就最先被聲明竞膳,又因為如果有多個函數(shù)聲明,則是由最后的一個函數(shù)聲明覆蓋之前所有的聲明诫硕,所以第9行的fn會覆蓋第3行的fn坦辟,相當(dāng)于fn這個函數(shù)的函數(shù)主題就是第9行下邊的內(nèi)容。緊接著就是fn被調(diào)用(第一行)章办,依次執(zhí)行锉走,輸出結(jié)果打印2,10(局部變量)纲菌。然后執(zhí)行到第14行的時候,因為變量提升疮绷,var fn 已經(jīng)提到頂部去了翰舌,所以這里只剩下賦值語句 fn = function{console.log('3')},相當(dāng)于給fn賦了一個新的值冬骚,就跟 var a = 1椅贱; a = 5 是一個道理。此時打印fn只冻,則輸出的是這個新的函數(shù)內(nèi)容 function{console.log('3')}庇麦。那么,如果在這后面再調(diào)用一次fn喜德,會輸出什么呢山橄?結(jié)果是3。因為此時fn就是一個新的函數(shù)了舍悯,他的主體部分就是打印字符3航棱。