詳解js 閉包(closure)
要想理解閉包這個詞,我們先來了解一下閉包的官方解釋。
「官方」的解釋是:所謂「閉包」,指的是一個擁有許多變數和綁定了這些變數的環境的表達式(通常是一個函數),因而這些變數也是該表達式的一部分。
但是我面試過程中聽到一個小哥說的閉包形成的條件,就是函數返回函數,這是我聽到的最容易理解的話。
官方的解釋對於新手是非常難理解的,我建議把這句話拆開來理解。
這句話去掉形容詞,就說閉包是表達式。怎麼理解閉包就是一個表達式,那就看看常見的閉包代碼吧。
function a(){ var i=0; function b(){ alert(++i); } return b; }
執行了a()()就是一個閉包。一般閉包就是一個函數,閉包還可以是下面的這種形式。
(function (){ var i=0; function b(){ alert(++i); } return b; })()();
在這裡首先明白了閉包是一個什麼主體了,這時候就開始了解閉包的形容詞。我們一個一個的理解。理解形容詞就要像剝洋蔥一樣去理解,先理解最外面的形容詞。閉包是一個擁有環境的表達式。這裡對於環境的理解是最重要的。
談到閉包就必須要了解js的作用域和變數提升的概念。js的作用域和變數提升概念細看可以到我的博客里仔細看一遍(後面持續更行)。這裡簡單的說一下變數的作用域和變數提升。
變數作用域:
首先js不同java和C這類的語言,js是沒有塊級作用域的。js是函數作用域。所謂的函數作用域就是在一個函數內不管哪裡定義的變數都可以在該函數內訪問。函數里是可以訪問函數外的變數,但是函數外就不能訪問函數里的顯示定義變數。自然,全局變數就不在這個範圍考慮了。
變數提升:
如果深刻理解了變數作用域,那麼變數提升也很簡單了。js可以在第一行訪問最後一行定義的變數,訪問的只是聲明的變數,賦值還是要按照代碼一行一行執行。為什麼js可以在一個函數里任何地方訪問任何地方的變數(這裡的任何地方排除函數里的函數),那是因為js函數作用域內把var聲明的變數都放在了執行語句前面。下面兩個例子來闡釋變數提升。
function(){ alert(a);//彈出undefind var a = 1;}
其實變數提升的本質是:
function(){ var a; alert(a);//彈出undefind a = 1;}//這樣是不是看的更加明白了。
但是這裡變數提升只是打個比喻,好讓大家明白js的函數作用域。其實實質不是這樣的,實質是每個函數都有個內存區域來存儲變數名。也就是一個存儲變數名的環境。在函數執行之前就已經把環境創建出來了。函數中如果要用到一個變數,那麼就會先到變數環境中查找,如果查找不到就往上查找,最後一直找到本地環境中。存儲變數名的內存區域其實就是作用域里的最核心概念了。
到了這裡是否理解了閉包是一個擁有環境的表達式這句話。這時候剩下的形容詞,許多變數是不是很容易理解。
我們順著來理解一遍,閉包就是一個擁有了環境綁定了許多變數的表達式。這樣是不是很容易理解了。
到這裡還不算了解完閉包,知道了閉包是什麼就要知道閉包是幹嘛用的。
閉包的作用:就是在a執行完並返回後,閉包使得Javascript的垃圾回收機制GC不會收回a所佔用的資源,因為a的內部函數b的執行需要依賴a中的變數。
閉包的應用場景:
- 保護閉包里的變數安全。因為只有閉包里的函數才能訪問閉包里的變數,所以外界就不能直接對閉包里的變數進行操作。
- 在內存中維持一個變數。單例模式是其中的經典模式。
推薦閱讀: