要知道這幾種寫法之間的區別,咱們要先聊些題外話——js中函數的兩種命名方式,即表達式和聲明式。javascript
函數的聲明式寫法爲:function foo(){/*...*/},這種寫法會致使函數提高,全部function關鍵字都會被解釋器優先編譯,不論是聲明在什麼位置,均可以調用它,可是它自己不會被執行,定義只是讓解釋器知道其存在,只有在被調用的時候纔會執行。java
圖1 聲明式函數瀏覽器
函數的表達式寫法爲:var foo=function(){/*...*/},這種寫法不會致使函數提高,因而就必須先聲明,再調用,不然會出錯,如圖2。閉包
圖2 表達式函數函數
如今,回到正題,(function(){}()),(function(){})()這兩種是js中當即執行函數的寫法,函數表達式後加上()能夠被直接調用,可是把整個聲明式函數用()包起來的話,則會被編譯器認爲是函數表達式,從而能夠用()來直接調用,如(function foo(){/*...*/})(),可是若是這個括號加在聲明式函數後面,如function foo(){/*...*/}(),則會報錯,不少博客說這種寫法()會被省略,但實際是會出錯,由於不符合js的語法,因此想要經過瀏覽器的語法檢查,就必須加點符號,好比()、+、!等,具體能夠查看圖3。spa
圖3 當即執行函數3d
總結一下就是:code
function foo(){console.log("Hello World!")}()//聲明函數後加()會報錯 (function foo(){console.log("Hello World!")}())//用括號把整個表達式包起來,正常執行 (function foo(){console.log("Hello World!")})()//用括號把函數包起來,正常執行 !function foo(){console.log("Hello World!")}()//使用!,求反,這裏只想經過語法檢查。 +function foo(){console.log("Hello World!")}()//使用+,正常執行 -function foo(){console.log("Hello World!")}()//使用-,正常執行 ~function foo(){console.log("Hello World!")}()//使用~,正常執行 void function foo(){console.log("Hello World!")}()//使用void,正常執行 new function foo(){console.log("Hello World!")}()//使用new,正常執行
當即執行函數通常也寫成匿名函數,匿名函數寫法爲function(){/*...*/},就是使用function關鍵字聲明一個函數,但未給函數命名,假若須要傳值,直接將參數寫到括號內便可如圖4所示。blog
圖4 當即執行函數的傳參事件
將它賦予一個變量則建立函數表達式,賦予一個事件則成爲事件處理程序等。可是須要注意的是匿名函數不能單獨使用,不然會js語法報錯,至少要用()包裹起來。上面的例子能夠寫成以下形式:
(function(){console.log("我是匿名函數。")}()) (function(){console.log("我是匿名函數。")})() !function(){console.log("我是匿名函數。")}() +function(){console.log("我是匿名函數。")}() -function(){console.log("我是匿名函數。")}() ~function(){console.log("我是匿名函數。")}() void function(){console.log("我是匿名函數。")}() new function(){console.log("我是匿名函數。")}()
當即執行函數的做用是:1.建立一個獨立的做用域,這個做用域裏面的變量,外面訪問不到,這樣就能夠避免變量污染。2.閉包和私有數據。提到閉包,不得不提下那道經典的閉包問題。
1 <ul id=」test」> 2 <li>這是第一條</li> 3 <li>這是第二條</li> 4 <li>這是第三條</li> 5 </ul> 6 7 <script> 8 var liList=document.getElementsByTagName('li'); 9 for(var i=0;i<liList.length;i++) 10 { 11 liList[i].onclick=function(){ 12 console.log(i); 13 } 14 }; 15 </script>
不少人以爲這樣的執行效果是點擊第一個li,則會輸出1,點擊第二個li,則會輸出二,以此類推。可是真正的執行效果是,無論點擊第幾個li,都會輸出3,如圖5所示。由於 i 是貫穿整個做用域的,而不是給每一個 li 分配了一個 i,用戶觸發的onclick事件以前,for循環已經執行結束了,而for循環執行完的時候i=3。
圖5 各自點擊第1,2,3個li,或是以後再次點了多少次,都會輸出3,可見,右邊控制檯輸出了8次3
可是若是咱們用了當即執行函數給每一個 li 創造一個獨立做用域,就能夠改寫爲下面的這樣,這樣就能實現點擊第幾條就能輸出幾的功能。
1 <script> 2 var liList=document.getElementsByTagName('li'); 3 for(var i=0;i<liList.length;i++) 4 { 5 (function(ii) { 6 liList[ii].onclick=function(){ 7 console.log(ii); 8 } 9 })(i) 10 }; 11 </script>
在當即執行函數執行的時候,i 的值被賦值給 ii,此後 ii 的值一直不變,如圖6所示。i 的值從 0 變化到 3,對應3 個當即執行函數,這 3個當即執行函數裏面的 ii 「分別」是 0、一、2。
圖6 點擊第幾個li,就輸出幾
其實ES6語法中的let也能夠實現上述的功能,僅僅是將for循環中的var換成let,以下所示,有木有以爲很簡單明瞭。
1 <script> 2 var liList=document.getElementsByTagName('li'); 3 for(let i=0;i<liList.length;i++) 4 { 5 liList[i].onclick=function(){ 6 console.log(i); 7 } 8 } 9 </script>
那不少人就以爲用let能夠徹底取代當即執行函數,到目前爲止,多是我眼界所限制,我所能用到的當即執行函數的確能被let替代,前提是你的運行環境(包括舊的瀏覽器)支持ES2015。若是不支持,你將不得不求助於之前經典的函數。