Javascript Function無(wú)處不在崎淳,而且功能強(qiáng)大!通過(guò)Javascript函數(shù)可以讓JS具有面向?qū)ο蟮囊恍┨卣髑汕冢瑢?shí)現(xiàn)封裝、繼承等弄匕,也可以讓代碼得到復(fù)用颅悉。但事物都有兩面性,Javascript函數(shù)有的時(shí)候也比較“任性”迁匠,你如果不了解它的“性情”签舞,它很可能給你制造出一些意想不到的麻煩(bugs)出來(lái)秕脓。
Javascript Function有兩種類(lèi)型:
1)函數(shù)聲明(Function Declaration);
// 函數(shù)聲明
function funDeclaration(type){ return type==="Declaration";
}
2)函數(shù)表達(dá)式(Function Expression)。
// 函數(shù)表達(dá)式
var funExpression = function(type){ return type==="Expression";
}
上面的代碼看起來(lái)很類(lèi)似儒搭,感覺(jué)也沒(méi)什么太大差別。但實(shí)際上芙贫,Javascript函數(shù)上的一個(gè)“陷阱”就體現(xiàn)在Javascript兩種類(lèi)型的函數(shù)定義上搂鲫。下面看兩段代碼(分別標(biāo)記為代碼1段和代碼2段):
funDeclaration("Declaration");//=> true
function funDeclaration(type){
return type==="Declaration";}
funExpression("Expression");//=>error
var funExpression = function(type){
return type==="Expression";
}
用函數(shù)聲明創(chuàng)建的函數(shù)funDeclaration可以在funDeclaration定義之前就進(jìn)行調(diào)用;而用函數(shù)表達(dá)式創(chuàng)建的funExpression函數(shù)不能在funExpression被賦值之前進(jìn)行調(diào)用磺平。
為什么會(huì)這樣呢魂仍?!這就要理解Javascript Function兩種類(lèi)型的區(qū)別:用函數(shù)聲明創(chuàng)建的函數(shù)可以在函數(shù)解析后調(diào)用(解析時(shí)進(jìn)行等邏輯處理)拣挪;而用函數(shù)表達(dá)式創(chuàng)建的函數(shù)是在運(yùn)行時(shí)進(jìn)行賦值擦酌,且要等到表達(dá)式賦值完成后才能調(diào)用。
這個(gè)區(qū)別看似微小菠劝,但在某些情況下確實(shí)是一個(gè)難以發(fā)現(xiàn)的陷阱赊舶。出現(xiàn)這個(gè)陷阱的本質(zhì)原因體現(xiàn)在這兩種類(lèi)型在Javascript function hoisting(函數(shù)提升)和運(yùn)行時(shí)機(jī)(解析時(shí)/運(yùn)行時(shí))上的差異。關(guān)于變量提升赶诊,可參見(jiàn)我的另外一篇博文http://www.cnblogs.com/isaboy/p/javascript_hoisting.html笼平。上面兩段代碼的函數(shù)提升可示意為下圖:
代碼1段JS函數(shù)等同于: </pre>
function funDeclaration(type){ return type==="Declaration";}
funDeclaration("Declaration");//=> true
代碼2段JS函數(shù)等同于
var funExpression;
funExpression("Expression");//==>error
funExpression = function(type){ return type==="Expression";
}
上述代碼在運(yùn)行時(shí),只定義了funExpression變量舔痪,但值為undefined寓调。因此不能在undefined上進(jìn)行函數(shù)調(diào)用。此時(shí)funExpression賦值語(yǔ)句還沒(méi)執(zhí)行到锄码。為了進(jìn)一步加深JS函數(shù)兩種類(lèi)型的區(qū)別夺英,下面給出一個(gè)更具迷惑性的示例,請(qǐng)看下面的代碼(代碼段4):
var sayHello;
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
function sayHey() {
console.log("sayHey");
}
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
function sayHey() {
console.log("sayHey2");
}
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
分析:sayHey是用函數(shù)聲明創(chuàng)建的滋捶,在JS解析時(shí)JS編譯器將函數(shù)定義進(jìn)行了函數(shù)提升痛悯,也就是說(shuō),在解析JS代碼的時(shí)候炬太,JS編譯器(條件判斷不形成新的作用域灸蟆,兩個(gè)sayHey函數(shù)定義都被提升到條件判斷之外)檢測(cè)到作用域內(nèi)有兩個(gè)同名的sayHey定義,第一個(gè)定義先被提升亲族,第二個(gè)定義接著被提升(第二個(gè)定義在第一個(gè)定義之下)炒考,第二個(gè)定義覆蓋了第一個(gè)sayHey定義,所以sayHey()輸出sayHey2霎迫;而sayHello是用函數(shù)表達(dá)式創(chuàng)建的斋枢,其表達(dá)式的內(nèi)容是在JS運(yùn)行時(shí)(不是解析時(shí))才能確定(這里條件判斷就起到作用了),所以sayHello表達(dá)式執(zhí)行了第一個(gè)函數(shù)定義并賦值知给,則sayHello()輸出sayHello
代碼段4的代碼實(shí)際上等同于下面的代碼(代碼段5):
var sayHello;
function sayHey() {
console.log("sayHey");
}
function sayHey() {
console.log("sayHey2");
}
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
有的人也許會(huì)懷疑函數(shù)sayHey的定義是第二個(gè)覆蓋第一個(gè)了么瓤帚?我們可以把sayHey的源代碼進(jìn)行輸出描姚,有圖有真相,如下圖所示:
總結(jié)
Javascript 中函數(shù)聲明和函數(shù)表達(dá)式是存在區(qū)別的戈次,函數(shù)聲明在JS解析時(shí)進(jìn)行函數(shù)提升轩勘,因此在同一個(gè)作用域內(nèi),不管函數(shù)聲明在哪里定義怯邪,該函數(shù)都可以進(jìn)行調(diào)用绊寻。而函數(shù)表達(dá)式的值是在JS運(yùn)行時(shí)確定,并且在表達(dá)式賦值完成后悬秉,該函數(shù)才能調(diào)用澄步。這個(gè)微小的區(qū)別,可能會(huì)導(dǎo)致JS代碼出現(xiàn)意想不到的bug,讓你陷入莫名的陷阱中和泌。
最后附上代碼段4中sayHello和sayHey兩個(gè)函數(shù)的核心步驟(個(gè)人理解村缸,若有異議歡迎留言探討):
上圖為sayHello函數(shù)執(zhí)行的主要步驟示意圖。