【JS】立即執(zhí)行函數(shù)(IIFE)/函數(shù)聲明/表達(dá)式解析

立即執(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)前外層上下文的變量保存起來善榛。

function expression Evaluation
function expression Evaluation

所以在這段代碼當(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末据途,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叙甸,更是在濱河造成了極大的恐慌颖医,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裆蒸,死亡現(xiàn)場離奇詭異熔萧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)僚祷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門佛致,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辙谜,你說我怎么就攤上這事俺榆。” “怎么了装哆?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵罐脊,是天一觀的道長。 經(jīng)常有香客問我烂琴,道長爹殊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任奸绷,我火速辦了婚禮梗夸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘号醉。我一直安慰自己反症,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布畔派。 她就那樣靜靜地躺著铅碍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪线椰。 梳的紋絲不亂的頭發(fā)上胞谈,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼烦绳。 笑死卿捎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的径密。 我是一名探鬼主播午阵,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼享扔!你這毒婦竟也來了底桂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤惧眠,失蹤者是張志新(化名)和其女友劉穎籽懦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锉试,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猫十,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年览濒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呆盖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贷笛,死狀恐怖应又,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乏苦,我是刑警寧澤株扛,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站汇荐,受9級(jí)特大地震影響洞就,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掀淘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一旬蟋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧革娄,春花似錦倾贰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厕妖,卻和暖如春首尼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工软能, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挠羔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓埋嵌,卻偏偏與公主長得像破加,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雹嗦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容