詳解立即執行函數(function(){}()),(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。如果不支持,你將不得不求助於以前經典的函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM