立即執(zhí)行函數(shù)(Immediately Invoked Function Expression,IIFE)
在JS中片橡,我們經(jīng)常會(huì)聽到一個(gè)名詞“立即執(zhí)行函數(shù)”荷辕,它們通常會(huì)以如下的語法形式出現(xiàn):
//IIFE普通函數(shù)
(function add(x,y){
console.log(x+y)
})(1,2);
(function add(x,y){
console.log(x+y)
}(1,2));
//IIFE匿名函數(shù)
(function(x,y){
console.log(x+y)
}(1,2));
(function(x,y){
console.log(x+y)
})(1,2);
IIFE函數(shù)的定義方式,可以讓函數(shù)在定義后就直接執(zhí)行籽孙,我們可以看到上面的代碼復(fù)制到console
中后尔觉,不需要另外調(diào)用函數(shù)设易,就會(huì)直接打印3
。
對(duì)比下普通的函數(shù)聲明和調(diào)用方法
function add(x,y){
console.log(x+y)
}
add();
//如果函數(shù)聲明之后直接調(diào)用澎灸,調(diào)用語句會(huì)被無視
function add(x,y){
console.log(x+y)
}(1,2);
//匿名函數(shù)賦值給變量院塞,可以正常調(diào)用
var addFunc = function(x,y){ console.log(x+y)};
addFunc(1,2);
//如果直接聲明匿名函數(shù)會(huì)直接報(bào)錯(cuò)
function(){ console.log('hahaha')};
function(){ console.log('hahaha')}();
//Uncaught SyntaxError: Function statements require a function name
function test(){ console.log('hahaha')}();
//Uncaught SyntaxError: Unexpected token ')'
我們可以看出,如果沒有外面的()
把函數(shù)體包裹起來性昭,函數(shù)是沒有辦法立刻執(zhí)行的拦止,匿名函數(shù)定義都會(huì)直接報(bào)錯(cuò),函數(shù)聲明后面跟著調(diào)用操作也會(huì)直接報(bào)錯(cuò)糜颠。
其中的原因是來自于:JS對(duì)于函數(shù)聲明和函數(shù)表達(dá)式語句的解析處理的方式是不同的汹族。
函數(shù)聲明(FunctionDeclaration )與函數(shù)表達(dá)式(Function Expression)
在JS中要定義一個(gè)函數(shù),有這么幾種方法:
- 函數(shù)構(gòu)造函數(shù)
使用構(gòu)造函數(shù)的過程其兴,我們可以理解為是調(diào)用表達(dá)式鞠抑,創(chuàng)建了新的對(duì)象實(shí)例,獲得的sum
對(duì)象剛好是個(gè)Function
的實(shí)例忌警。
var sum = new Function('a','b', 'return a + b;');
alert(sum(10, 20)); //alerts 30
- 函數(shù)聲明
function sum(a, b){
return a + b;
}
alert(sum(10, 10)); //20
JS中如果形如行首以function
關(guān)鍵字開頭搁拙,且?guī)в泻瘮?shù)名(Identifier
),則解析為是函數(shù)聲明法绵。如以下形式:
function Identifier ( FormalParameterListopt ){ FunctionBody }
所以箕速,當(dāng)我們直接定義一個(gè)匿名函數(shù),因?yàn)橐彩?code>function開發(fā)朋譬,JS會(huì)按照函數(shù)聲明的標(biāo)準(zhǔn)去解析它盐茎,解析后發(fā)現(xiàn)它沒有設(shè)置函數(shù)名,就會(huì)直接報(bào)錯(cuò)徙赢。
JS在解析函數(shù)聲明的時(shí)候字柠,存在變量提升的情況探越。
sum(1,2);//sum還未定義,也可以輸出3
console.log(sum);
function sum(x,y){
console.log(x+y);
}
- 函數(shù)表達(dá)式
使用函數(shù)表達(dá)式來獲得一個(gè)函數(shù)窑业。
var sum = function(a, b) { return a + b; }
alert(sum(5, 5)); // alerts 10
var sum = function sumCopy(a, b) { return a + b; }
alert(sum(5, 5)); // alerts 10
JS中=
是賦值操作符钦幔,說明該語句是一句表達(dá)式而不是聲明。表達(dá)式后面跟著的函數(shù)常柄,可以帶函數(shù)名也可以不帶鲤氢。
函數(shù)表達(dá)式和函數(shù)聲明有什么區(qū)別呢?
我們看下兩段代碼的對(duì)比:
- 函數(shù)聲明
function sumDeclaration(a, b) {
console.log(typeof sumDeclaration);
return a + b;
}
var sum = sumDeclaration;
sum(1,2);//輸出function
console.log(typeof sumDeclaration);//輸出function
sumDeclaration
函數(shù)聲明之后西潘,它是被掛在外層的全局變量當(dāng)中的卷玉,它是屬于它所在的整個(gè)上下文的,所以我們不僅可以在它的函數(shù)內(nèi)部去訪問到它喷市,也可以在外層上下文訪問到它相种。
- 普通表達(dá)式
(1+3);
var a = 1+5;
console.log(a);
(var a = 1+5);//Uncaught SyntaxError: Unexpected token 'var'
當(dāng)我們?cè)?code>console里面直接執(zhí)行表達(dá)式代碼的時(shí)候,我們會(huì)發(fā)現(xiàn)操作符會(huì)自動(dòng)執(zhí)行品姓,所以a會(huì)直接輸出5蚂子。1+3輸入也會(huì)去計(jì)算。
所以如果JS解析到是個(gè)表達(dá)式語句缭黔,它會(huì)去把語句中可執(zhí)行的操作符執(zhí)行食茎。
我們發(fā)現(xiàn),如果把帶有聲明的語句如var等
馏谨,用()
包裹起來别渔,會(huì)直接報(bào)錯(cuò)。
此時(shí)JS不會(huì)把這個(gè)語句當(dāng)作聲明去解析惧互,而是當(dāng)作表達(dá)式來解析哎媚,表達(dá)式中又沒有var
的執(zhí)行邏輯,就報(bào)錯(cuò)了喊儡。
- 函數(shù)表達(dá)式
console.log(sum);//undefined
console.log(sumCopy);//Error:sumCopy is not defined
var sum = function sumCopy(a, b) {
console.log(typeof sumCopy);
return a + b;
}
sum(1,2);//輸出function
console.log(sumCopy);//Error:sumCopy is not defined
我們會(huì)發(fā)現(xiàn)拨与,在定義之前我們可以訪問到sum
變量,因?yàn)樗呀?jīng)被var
聲明了艾猜,也存在變量提升买喧,所以sum
獲取到的是undefined
,但是sumCopy
在第三行代碼執(zhí)行之前引用就會(huì)直接報(bào)錯(cuò)匆赃。
因?yàn)橛倜琂S解析函數(shù)表達(dá)式的時(shí)候是按照順序在執(zhí)行解析的,沒有存在變量提升算柳。
而且sumCopy這個(gè)函數(shù)沒有被當(dāng)成函數(shù)聲明被解析低淡,對(duì)于它的外部環(huán)境來說,它不會(huì)作為一個(gè)函數(shù)變量掛在到外部環(huán)境的詞法環(huán)境里面。因?yàn)樗J(rèn)為你在執(zhí)行一個(gè)具體的操作語句而不是聲明操作蔗蹋。
但是function
關(guān)鍵詞會(huì)創(chuàng)建新的作用域何荚,所以在sumCopy
內(nèi)部再訪問它,會(huì)輸出function
猪杭,因?yàn)?code>sumCopy在它自己新創(chuàng)建的作用域里面函數(shù)變量名掛在到它自己的詞法作用域了餐塘。
我的理解是,當(dāng)使用函數(shù)表達(dá)式的時(shí)候胁孙,可以理解為給sum
這個(gè)變量賦值一個(gè)function對(duì)象唠倦。除非你引用sum
這個(gè)對(duì)象称鳞,否則這個(gè)function對(duì)象涮较,不能夠被它表達(dá)式語句作用域以外的作用域去使用。
Why do you need to invoke an anonymous function on the same line?
深入理解javascript中的立即執(zhí)行函數(shù)(function(){…})()
所以如果我們希望冈止,函數(shù)定義完就執(zhí)行掉狂票,不要被外層的任何對(duì)象保存∥醣可以怎么實(shí)現(xiàn)呢闺属?
結(jié)合前面說的表達(dá)式會(huì)執(zhí)行操作符和函數(shù)表達(dá)式的特點(diǎn),所以在JS中周霉,我們只要讓JS在解析的時(shí)候掂器,不要認(rèn)為這是函數(shù)聲明,而認(rèn)為是函數(shù)表達(dá)式俱箱,并且不要把函數(shù)表達(dá)式保存下來不就可以了国瓮?
IIFE實(shí)現(xiàn)
我們可以借助普通表達(dá)式,把函數(shù)聲明改造成函數(shù)表達(dá)式狞谱。
-function test(){console.log('test');}();
+function test(){console.log('test');}();
(function test(){console.log('test');}());
console.log(test);//Uncaught ReferenceError: test is not defined
我們發(fā)現(xiàn)乃摹,這三個(gè)語句都可以成功執(zhí)行,而且test函數(shù)沒有被外部保存下來跟衅。
所以說孵睬,關(guān)鍵就在于讓JS認(rèn)為這句代碼不是個(gè)函數(shù)聲明而是個(gè)表達(dá)式。
(function test(){console.log('test');})()
(functionbody)()
這種形式伶跷,可以理解為()
讓JS認(rèn)為內(nèi)部是個(gè)函數(shù)表達(dá)式掰读,所以返回了個(gè)函數(shù)對(duì)象,functionObj()
就直接執(zhí)行了叭莫。
[譯] JavaScript:立即執(zhí)行函數(shù)表達(dá)式(IIFE)
IIFE作用域問題
在ECMA文檔中磷支,當(dāng)函數(shù)表達(dá)式執(zhí)行的時(shí)候,我們可以看到食寡,會(huì)把當(dāng)前詞法環(huán)境的變量雾狈,賦給執(zhí)行上下文的詞法環(huán)境。也可以解釋為抵皱,它會(huì)把當(dāng)前外層上下文的變量保存起來善榛。
所以在這段代碼當(dāng)中:
for(var i = 0;i < 5; i++ ) {
(function(i){
setTimeout(function(){
console.log(i);
},1000)
})(i);
}
通過()
把function(i)變成了函數(shù)表達(dá)式辩蛋,調(diào)用時(shí)傳入了每輪循環(huán)中的i
,每輪循環(huán)中i的數(shù)值會(huì)被函數(shù)表達(dá)式保存在執(zhí)行上下文的詞法環(huán)境當(dāng)中移盆,所以每次執(zhí)行的時(shí)候悼院,在事件循環(huán)隊(duì)列當(dāng)中的每個(gè)任務(wù),對(duì)應(yīng)的詞法環(huán)境都是對(duì)應(yīng)的循環(huán)時(shí)的變量咒循。
ECMA-sec-function-definitions-runtime-semantics-evaluation
What is an IIFE in JavaScript?
JS中的IIFE
JavaScript系列之立即執(zhí)行函數(shù)IIFE