2020年6月補充
這篇文章算是帶我入JavaScript甚至是前端的門的第一文,當時還在做實驗室的一個項目需要在地圖上為每個信號塔設置定時器顯示一些自定義的動畫,結構類似下圖的代碼。 最近看了《你不知道的JavaScript 上卷》中閉包的內容還有掘金中一位博主整理的閉包面試題,再聯系聯系Event Loop,甚至了解了ES6之后的let、箭頭函數后,覺得還是有必要回過頭來補充些東西(關鍵詞:閉包、Event Loop、let、箭頭函數)
初學者可以直接從【什么是匿名函數】這部分開始看,然后再回過頭來看我這塊補充的內容,就當為后續深入了解指路了。
思考以上代碼為什么輸出總是6。這里涉及到了JS的運行機制,在此簡單做下引導,具體戳相關鏈接。
因為setInterval為宏任務,由於JS中單線程eventLoop機制,在主線程同步任務執行完后才去執行宏任務,因此循環結束后setInterval中的回調才依次執行,但輸出i的時候當前作用域沒有,往上一級再找,發現了i,此時循環已經結束,i變成了6。因此會全部輸出6。
當然學了立即執行函數我們會使用立即執行函數給每個li創造一個獨立作用域就能解決。為啥要了解立即執行函數呢,不就是為了解決問題嗎,那還有別的方案嗎?
這里再給一個方案:使用ES6的let定義i
相關鏈接:
以下是正文
問題引入:我們寫函數,就是為了使我們的代碼更加模塊化,然后,提高代碼的重用。但是,有些函數,從定義到整個函數就運行了一遍。但是這個函數依然存在,就占用了大量的內存。那有沒有一種函數,執行完了之后,就不存在了的呢?
什么是匿名函數
聲明一個函數,並馬上調用這個匿名函數就叫做立即執行函數;也可以說立即執行函數是一種語法,讓你的函數在定義以后立即執行;
立即執行函數的創建步驟,看下圖:
立即函數形式
接下來看立即執行函數的兩種常見形式:
//匿名函數包裹在一個括號運算符中,后面跟一個小括號 (function(){ //... })() ////匿名函數后面跟一個小括號,整個包裹在一個括號運算符中 (function(){ //... }())
(),!,+,-,=等運算符都能起到立即執行的作用,這些運算符的作用就是將匿名函數或函數聲明轉換為函數表達式。
要注意兩點,一是函數體后面要有小括號(),二是函數體必須是函數表達式而不能是函數聲明。
(function (test) { //使用()運算符,輸出123 console.log(test); })(123); (function (test) { //使用()運算符,輸出123 console.log(test); }(123)); !function (test) { //使用!運算符,輸出123 console.log(test); }(123); var fn = function (test) { //使用=運算符,輸出123 console.log(test); }(123);
好處
- 不必為函數命名,避免了污染全局變量
- 立即執行函數內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量
- 封裝變量
總而言之:立即執行函數會形成一個單獨的作用域,我們可以封裝一些臨時變量或者局部變量,避免污染全局變量。以一個面試題為例:
var liList = ul.getElementsByTagName('li') for(var i=0; i<6; i++){ liList[i].onclick = function(){ alert(i) // 為什么 alert 出來的總是 6,而不是 0、1、2、3、4、5 } }
為什么 alert 的總是 6 呢,因為 i 是貫穿整個作用域的,而不是給每個 li 分配了一個 i,如下:
划重點:用戶一定是在for運行完了之后,才點擊的,此時i為6
解決方案:
用立即執行函數給每個li創造一個獨立作用域即可(當然還有其他辦法):
var liList = ul.getElementsByTagName('li') for(var i=0; i<6; i++){ !function(ii){ liList[ii].onclick = function(){ alert(ii) // 0、1、2、3、4、5 } }(i) }
使用場景
2、所有的這些工作只需要執行一次,比如只需要顯示一個時間。
3、但是這些代碼也需要一些臨時的變量,但是初始化過程結束之后,就再也不會被用到,如果將這些變量作為全局變量,不是一個好的注意,我們可以用立即執行函數——去將我們所有的代碼包裹在它的局部作用域中,不會讓任何變量泄露成全局變量。看如下代碼:
