js和其他語言一樣,都要經(jīng)歷編譯和執(zhí)行階段。而js在編譯階段的時(shí)候,會(huì)搜集所有的變量聲明并且提前聲明變量,而其他的語句都不會(huì)改變他們的順序,因此,在編譯階段的時(shí)候,第一步就已經(jīng)執(zhí)行了,而第二步則是在執(zhí)行階段執(zhí)行到該語句的時(shí)候才執(zhí)行
也就是說,在編譯的時(shí)候,頁面上所有的變量都已經(jīng)被聲明了,但是都還沒有賦值,即數(shù)據(jù)類型暫為undefined。
例1
a = 5
var a
console.log(a)
那么這里會(huì)打印出什么呢?按照正常的從上到下的邏輯思路,第2行的a并沒有被定義,所以第四行打印a時(shí)應(yīng)該會(huì)報(bào)錯(cuò)說 a is not defined,但是,注意注意,這里的a被提升了。
“變量提升會(huì)將當(dāng)前作用域的所有變量的聲明提升到程序的頂部”,意思就是說,雖然在第2行的a沒有用關(guān)鍵字 var 定義,但是編譯時(shí)會(huì)查找所有的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。
理解到簡單的定義之后,就要思考一下相對(duì)比較復(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ù)的時(shí)候,老師跟我們說,形參就相當(dāng)于是在函數(shù)內(nèi)部定義了一個(gè)變量,就是var 了一個(gè)變量;然后實(shí)參傳進(jìn)來就相當(dāng)于是給這個(gè)變量賦了值。接下來我們就可以沿著這個(gè)思路去分析。還有一點(diǎn):參數(shù)名和變量名同名,參數(shù)優(yōu)先級(jí)高于變量提升
因?yàn)閰?shù)變量提升優(yōu)先級(jí)高,所以在函數(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)鍵字,此時(shí),當(dāng)show函數(shù)被調(diào)用后,a就被添加到了window對(duì)象中,成為了一個(gè)全局變量,全局變量在script的任何地方都能被訪問,所以最后一行,在函數(shù)外部也能訪問 a,結(jié)果打印10(如果前面沒有調(diào)用show,即show(),那么a就沒被創(chuàng)建,即未定義)
第三種情況:函數(shù) 有/無 參數(shù),函數(shù)內(nèi)部的變量以 var 定義
此時(shí)的var 后面的變量就是一個(gè)局部變量,也就是說,這個(gè)變量只能在其作用于內(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)局部變量和全局變量都有提升時(shí),優(yōu)先考慮局部變量提升。
第五種情況:函數(shù)提升跟變量提升的變量重名
函數(shù)也是一個(gè)變量,可以用函數(shù)聲明的方式定義,也可以用函數(shù)表達(dá)式的方式定義,所以也存在函數(shù)提升。
當(dāng)有多個(gè)同名變量聲明的時(shí)候,函數(shù)聲明會(huì)覆蓋其他的聲明。如果有多個(gè)函數(shù)聲明,則是由最后的一個(gè)函數(shù)聲明覆蓋之前所有的聲明。
舉個(gè)例子來解釋一下:
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。因?yàn)槌霈F(xiàn)變量重名的情況時(shí),函數(shù)聲明高于所有變量聲明,即函數(shù)聲明最先實(shí)現(xiàn),那么第3行和第9行的function fn就最先被聲明,又因?yàn)?strong>如果有多個(gè)函數(shù)聲明,則是由最后的一個(gè)函數(shù)聲明覆蓋之前所有的聲明,所以第9行的fn會(huì)覆蓋第3行的fn,相當(dāng)于fn這個(gè)函數(shù)的函數(shù)主題就是第9行下邊的內(nèi)容。緊接著就是fn被調(diào)用(第一行),依次執(zhí)行,輸出結(jié)果打印2,10(局部變量)。然后執(zhí)行到第14行的時(shí)候,因?yàn)樽兞刻嵘?,var fn 已經(jīng)提到頂部去了,所以這里只剩下賦值語句 fn = function{console.log('3')},相當(dāng)于給fn賦了一個(gè)新的值,就跟 var a = 1; a = 5 是一個(gè)道理。此時(shí)打印fn,則輸出的是這個(gè)新的函數(shù)內(nèi)容 function{console.log('3')}。那么,如果在這后面再調(diào)用一次fn,會(huì)輸出什么呢?結(jié)果是3。因?yàn)榇藭r(shí)fn就是一個(gè)新的函數(shù)了,他的主體部分就是打印字符3。