【技術雜貨鋪】JS中的閉包是個什麼東西
我自己想要了解到閉包這個概念是因為在上一篇隨筆,介紹防抖和節流的時候,有一段程式碼看得我有點犯迷糊,如下
function debounce(fn, delay) { var timer = null; return function() { if (timer) clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); } }
按照我之前的瞭解,每次呼叫這個debounce防抖函式的時候,timer都要被重新賦值為null,這怎麼起得到定時器的作用呢?後來瞭解到這是因為閉包的存在,所以timer這個變數在記憶體一直儲存下來,而不是重新清空。好了,下面正式介紹閉包就能知道我在嘮叨什麼了。
要說清楚這個問題,我覺得要先從基礎說起,並且清楚裡面的一些細節,才能理解清楚。js的變數分為全域性變數和區域性變數。
全域性變數:可以在任意位置訪問的變數就是全域性變數。例如下面的age。
var age = 10; function a(){ console.log(age); //輸出為10 }
區域性變數:函式中用var定義的變數,只能在函式中訪問這個變數,函式外部訪問不了。
function a(){ var age = 10; } a(); console.log(age); //Uncaught ReferenceError: age is not defined
OK,上面這些大家應該都是很熟悉的了,那麼有兩個注意點要注意,
1.在函式中如果不適用var定義變數那麼js引擎會自動將該變數變為全域性變數。
2.全域性變數自建立起就一直儲存在記憶體,直到你關閉頁面,而區域性變數是當其所在的函式執行完後就會銷燬,假如再次呼叫該函式,會再次重新建立這個變數,即執行完就銷燬,回到最初的狀態。
再來說說函式,一個函式可以套很多別的函式,函式裡面的子函式可以訪問到它上級函式定義的變數,這個上級可以像冒泡一樣,一直往上找,如果找到全域性變數都找不到,那麼就會報錯。
閉包
下面要通過一個例子來引入閉包
function a(){ varcount = 0; console.log(++count); } a(); //輸出1 a(); //還是輸出1
上面的程式碼輸出了兩個1,是因為上面的區域性變數在函式執行完一次後就會銷燬,不會儲存在記憶體,再次呼叫時會重新建立那個變數。那麼問題來了,我們怎樣做才能確保第一次的建立的變數不會被銷燬呢,就是說,如果讓第二次呼叫該函式時,輸出的是2,不是1呢?這就需要我們的閉包出來了。
Warning:JavaScript中有回收機制,函式沒有被引用執行完以後這個函式的作用域就會被銷燬,如果一個函式被其他變數引用(重要),這個函式的作用域將不會被銷燬,(簡單來說就是函式裡面的變數會被儲存下來,你可以理解成全域性變數。)
好了,下面請出我們的閉包:
function a(){ var count = 0; function b(){ count ++; console.log(count); } return b; } var ab = a(); ab(); //輸出1 ab(); //輸出2
這裡可以看到,裡面的變數count的值並沒有重新計算,因為函式a被外部的變數ab引用,所以變數count沒有被回收。
如果某個函式被它的父函式之外的一個變數引用,就形成了一個閉包。
還有一種更為常用的閉包寫法:
var ab = (function(){ var count = 0; function b(){ count++; console.log(count); } return b; })(); bi(); //1 bi(); //2 bi(); //3
執行過程分析:首先把一個自執行函式賦值給了ab,這個自執行函式執行完後ab的值就變為了
function b(){ count++; console.log(count); }
因為我們在上面的程式碼return回去了b,表示將b賦值給了ab,然後因為這個自執行函式被ab引用所以裡面的count變數並沒有因為這個自執行函式執行完而銷燬,而是儲存到了記憶體中,所以多次列印會累加。