要知道這幾種寫法之間的區別,我們要先聊些題外話——js中函數的兩種命名方式,即表達式和聲明式。 函數的聲明式寫法為:function foo(){/*...*/},這種寫法會導致函數提升,所有function關鍵字都會被解釋器優先編譯,不管是聲明在什麼位置,都可以調用它,但是它本身不會被執行,定義只 ...
要知道這幾種寫法之間的區別,我們要先聊些題外話——js中函數的兩種命名方式,即表達式和聲明式。
函數的聲明式寫法為:function foo(){/*...*/},這種寫法會導致函數提升,所有function關鍵字都會被解釋器優先編譯,不管是聲明在什麼位置,都可以調用它,但是它本身不會被執行,定義只是讓解釋器知道其存在,只有在被調用的時候才會執行。
圖1 聲明式函數
函數的表達式寫法為:var foo=function(){/*...*/},這種寫法不會導致函數提升,於是就必須先聲明,再調用,否則會出錯,如圖2。
圖2 表達式函數
現在,回到正題,(function(){}()),(function(){})()這兩種是js中立即執行函數的寫法,函數表達式後加上()可以被直接調用,但是把整個聲明式函數用()包起來的話,則會被編譯器認為是函數表達式,從而可以用()來直接調用,如(function foo(){/*...*/})(),但是如果這個括弧加在聲明式函數後面,如function foo(){/*...*/}(),則會報錯,很多博客說這種寫法()會被省略,但實際是會出錯,因為不符合js的語法,所以想要通過瀏覽器的語法檢查,就必須加點符號,比如()、+、!等,具體可以查看圖3。
圖3 立即執行函數
總結一下就是:
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所示。
圖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、1、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。如果不支持,你將不得不求助於以前經典的函數。