Js代碼分為兩個(gè)階段:編譯階段和執(zhí)行階段
Js代碼的編譯階段會(huì)找到所有的聲明,并用合適的作用域?qū)⑺鼈冴P(guān)聯(lián)起來莲镣。
包括變量聲明(var a)和函數(shù)聲明(function a(){})在內(nèi)的所有聲明都會(huì)在代碼被執(zhí)行前的編譯階段首先被處理诈嘿。這個(gè)過程就好像變量聲明和函數(shù)聲明從他們代碼中出現(xiàn)的位置被添加到最近執(zhí)行環(huán)境的頂部述呐,這個(gè)過程就叫做提升(hoisting)篮迎。
只有聲明操作會(huì)被提升甫菠,賦值和邏輯操作會(huì)被留在原地等待執(zhí)行仔粥。
變量的聲明
js中婴谱,在使用一個(gè)變量之前應(yīng)當(dāng)先聲明蟹但。變量是使用關(guān)鍵字var聲明的,如:
var i;
var sum;
注:此時(shí)console.log(i)=undefined谭羔,因?yàn)榇藭r(shí)的變量沒有被賦值华糖,默認(rèn)初始值是undefined。
也可以使用一個(gè)var關(guān)鍵字聲明多個(gè)變量:
var i,sum;
而且還可以將變量的初始賦值和變量聲明合寫在一起:
var m="hello";
var i = 0,j = 0,k = 0;
到這里你大概會(huì)發(fā)現(xiàn)js的變量聲明中沒有指定數(shù)據(jù)類型瘟裸,就比如在C語言中客叉,我們定義一個(gè)變量常常是int a;char c;。话告。兼搏。。這里的int char 就是所謂的數(shù)據(jù)類型
小知識(shí):編程語言分為動(dòng)態(tài)(類型)語言和靜態(tài)(類型)語言沙郭,動(dòng)態(tài)類型語言是指在運(yùn)行期間才會(huì)去做數(shù)據(jù)類型檢查的語言佛呻,也就是說,在用動(dòng)態(tài)類型的語言編程的時(shí)病线,永遠(yuǎn)也不用給任何變量指定數(shù)據(jù)類型吓著,該語言會(huì)在第一次賦值給變量時(shí),在內(nèi)部將數(shù)據(jù)類型記錄下來送挑。靜態(tài)類型語言與動(dòng)態(tài)類型語言剛好相反绑莺,它的數(shù)據(jù)類型是在編譯期間檢查的,也就是說在寫程序時(shí)要聲明所有變量的數(shù)據(jù)類型让虐。
靜態(tài)類型語言:C/C++紊撕、C#、JAVA等
動(dòng)態(tài)類型語言:Python赡突、Ruby、JavaScript等
變量聲明提升
舉個(gè)例子:
var a = 1;
var b =2;
上述兩行代碼在編譯時(shí)区赵,實(shí)際上它的順序是這樣的:
var a;? ? //undefined
var b;? ? //undefined
a = 1;? ? //1
b = 2;? ? //2
解釋:編譯器將var a = 1;這行代碼分成兩步操作惭缰,
第一步是變量的聲明提升:var a;聲明操作會(huì)在編譯階段進(jìn)行笼才,所有聲明會(huì)被提升到最近執(zhí)行環(huán)境的頂部漱受,此時(shí)的變量值是初始值undefined。
第二步是常規(guī)的賦值操作:a = 0;
這里有一點(diǎn)要注意骡送,JavaScript中聲明變量可以不寫前面的var關(guān)鍵字昂羡,這種是合法且可行的,去掉var時(shí)摔踱,會(huì)被默認(rèn)成全局變量虐先,但是我們不建議這樣使用,這個(gè)不好的習(xí)慣會(huì)造成很多bug派敷。嚴(yán)格模式下蛹批,不聲明就使用一個(gè)變量也會(huì)導(dǎo)致錯(cuò)誤撰洗。比如:
function add(num1,num2){
var sum = num1 + num2;
return sum;
}
var result = add(10,20);? ? ? //30
alert(sum);? ? ? ? ? ? ? ? ? ? ? ? //由于sum在函數(shù)體內(nèi)被聲明過,在全局不是有效的變量腐芍,因此會(huì)導(dǎo)致錯(cuò)誤
改成:
function add(num1,num2){
sum = num1 + num2;
return sum;
}
var result = add(10,20);? ? ? //30
alert(sum);? ? ? ? ? ?//30 此時(shí)的sum是全局變量差导,所以這里可以訪問的到。但是不推薦猪勇。
函數(shù)的聲明
定義函數(shù)有三種方式:函數(shù)聲明设褐、函數(shù)表達(dá)式、Function構(gòu)造函數(shù)(不推薦)
函數(shù)聲明提升會(huì)在編譯階段把聲明和函數(shù)體整體都提前到執(zhí)行環(huán)境頂部泣刹,所以我們可以在函數(shù)聲明之前調(diào)用這個(gè)函數(shù)助析。比如:
function sum(num1,num2){
return num1 + num2;
}
函數(shù)表達(dá)式,其實(shí)就是變量聲明的一種项玛,聲明操作會(huì)被提升到執(zhí)行環(huán)境頂部貌笨,并賦值undefined。賦值操作被留在原地等到執(zhí)行襟沮。這種定義方式得到的函數(shù)也叫匿名函數(shù)(拉姆達(dá)函數(shù))锥惋,因?yàn)閒unction關(guān)鍵字后面沒有函數(shù)名字,只是把這個(gè)函數(shù)體賦值給一個(gè)變量开伏。這種方式定義函數(shù)也沒有必要使用函數(shù)名---通過變量名就可以引用函數(shù)膀跌。另外還要注意,此時(shí)函數(shù)末尾有一個(gè)分號(hào)固灵,就像聲明其他變量一樣需要一個(gè)分號(hào)作為結(jié)尾捅伤。比如:
var sum = function (num1,num2){
return num1 + num2;
};
Function構(gòu)造函數(shù)可以接收任意數(shù)量的參數(shù),但最后一個(gè)參數(shù)始終都被看成函數(shù)體巫玻,而前面的參數(shù)枚舉出了新函數(shù)的參數(shù)丛忆。比如:
var sum = new Function("num1","num2","return num1 + num2");? ? ? ? //不推薦
從技術(shù)上講,這是一個(gè)函數(shù)表達(dá)式仍秤,但是這種語法會(huì)導(dǎo)致解析兩次代碼(第一次是解析常規(guī)的ES代碼熄诡,第二次是解析傳入構(gòu)造函數(shù)中的字符串),從而影響性能诗力。不過凰浮,這種語法對(duì)于理解“函數(shù)是對(duì)象,函數(shù)名是指針”的概念倒是非常直觀的苇本。
函數(shù)的聲明提升
小知識(shí):在一些類似C語言的編程語言中袜茧,花括號(hào)內(nèi)的每一段代碼都是具有各組的作用域,而且變量在聲明它們的代碼段之外是不可見的瓣窄。我們稱之為塊級(jí)作用域(block scope)笛厦,然而,JavaScript是沒有塊級(jí)作用域康栈。JavaScript取而代之地使用了函數(shù)作用域(function scope):變量在聲明它們的函數(shù)體以及這個(gè)函數(shù)體嵌套的任意函數(shù)體內(nèi)都是有定義的递递。
function test(0){
var i = 0;? ? ? ? //i在整個(gè)函數(shù)體內(nèi)均有定義
? ? if(typeof 0 == "object"){
? ? var j = 0;? //j在整個(gè)函數(shù)體內(nèi)均有定義喷橙,不僅僅是在這個(gè)代碼段內(nèi)
for(var k = 0;k < 10;k++){? //j在整個(gè)函數(shù)體內(nèi)均有定義,不僅僅是在這個(gè)循環(huán)內(nèi)
? ? ? ? console.log(k);? ? ? ? ? //輸出數(shù)字0--9
? ? }
? ? ? console.log(k);? ? ? ? ? ? ? ? ? //k已經(jīng)定義了登舞,輸出10
? ? ? ? ? }
? ? console.log(j);? ? ? ? ? ? ? ? ? ? ? ? //j已經(jīng)定義了贰逾,但可能沒有初始化
}
JavaScript的函數(shù)作用域是指在函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的,所以當(dāng)在函數(shù)體內(nèi)使用各種for嵌套循環(huán)時(shí)應(yīng)定義不同的變量i菠秒,j疙剑,k等。
舉個(gè)例子:
var scope = "global";
function f(){
console.log(scope);? ? ? ? ? ? ? //undefined
var scope = "local";? ? ? ? ? ? ? //變量在這里賦值践叠,但是在整個(gè)函數(shù)體內(nèi)都是有定義的
console.log(scope);? ? ? ? ? ? ? //local
}? ? ? ? ?
你可能會(huì)認(rèn)為第一個(gè)console會(huì)輸出“global”言缤,但是由于函數(shù)作用域的特性,局部變量在整個(gè)函數(shù)體內(nèi)都是有定義的禁灼,上述代碼等價(jià)于:
var scope = "global";
function f(){
var scope;? ? ? ? ?//undefined管挟,因?yàn)楹瘮?shù)聲明優(yōu)先級(jí)更高,所以覆蓋了第一行的賦值
console.log(scope);? ? ? ? ? ? ? ? //變量存在弄捕,但其值是初始值undefined
scope = "local";? ? ? ? ? ? ? ? ? //變量在這里賦值為local
console.log(scope);? ? ? ? ? ? ? ? //local
}
最后總結(jié)僻孝,聲明的順序是這樣的:
第一步. 找到所有的函數(shù)聲明,初始化函數(shù)體守谓,如有同名的函數(shù)則會(huì)進(jìn)行覆蓋
第二步. 查找變量聲明穿铆,初始化為undefined,如果已經(jīng)存在同名的變量斋荞,就什么也不做直接略過荞雏。
要注意咯,函數(shù)聲明的優(yōu)先級(jí)比變量聲明的優(yōu)先級(jí)高平酿,如果是先聲明并賦值了一個(gè)變量凤优,后面的函數(shù)體內(nèi)又聲明了同名變量,此時(shí)的變量就被函數(shù)體內(nèi)的變量覆蓋了蜈彼。