Outline
- Closure 的形成
- 經典範例
Closure 的形成
函數內的變數在函式執行完之後,就無法再被參照到,這個時候一開始被分派的記憶體就會被釋放什麼意思呢?
function getEnemyInfo(){
let enemies = ['Darth Vader','Sheev Palpatine'];
let enemyLeader = 'Sheev Palpatine'
return enemies
}
function getBattleInfo(){
let fellowInfo = ['Clone' , 'Clone' , 'Warship', 'Clone'];
let enenyInfo = getEnemyInfo()
// enemyLeader is not defined
// because it's located in another function.
console.log(enemyLeader)
return `the number of fellows is: ${fellowInfo.length }, and
the number of enemies is" ${enenyInfo.length}`
}
getBattleInfo()
先來了解一下基本觀念,如上述例子,getEnemyInfo 函式裡面宣告的變數,在函式執行環境結束後(執行完),就再也無法取得,( 也可以說該變數的有效範圍只存在於該函式內 ),因為這時執行堆疊已經剩下全域。除非我把該變數宣告在全域,否則在外面是無法拿到的。
下面來看一個經典的 Closure 例子:
let country = 'United Nations'
let soilder = ['Clone' , 'Clone' , 'Warship', 'Clone'];
let jedi = ['Yoda' , 'Obi-Wan', 'Anakin']
function addA(numA){
return function (numB){
return numA+numB
}
}
let addB = addA(jedi.length)
let fellowNum = addB(soilder.length)
上面是一個要把兩個數字加起來的 add 函式。 這個函式會返回另外一個函式,之後才會真正把兩個數字加起來,在我們輸入第一個參數之後,就會結束該函式執行環境並返回另外一個函式。
照理說當 addA 函式回傳第二個函式之後,addA 的執行環境就結束,就沒辦法拿到該函式參數 numA ,但對 addB 函式而言,在其內部有引用到他內部沒有的變數 (numA),因此他會轉為向外部環境( Scpoe Chain )尋找 ,JS 引擎就會為此保留這個函式的記憶體空間,不會釋放。
這看起來就 numA 在 addB 執行環境存在時,暫時為了 addB 而被保留,完全只屬於 addB ,所以這個暫時存在的封閉環境,就被稱為「閉包 ( Closure )」。
經典範例
接下來我們要來看一個很常見,且非常容易讓人誤解的例子:
function pushFuncToArray(){
var funcArr = []
for (var i=0; i<3; i++){
funcArr.push(function(){
console.log(i)
})
}
return functionArr
}
var functionArr = pushFuncToArray()
functionArr[0]()
functionArr[1]()
functionArr[2]()
如果你沒有接觸過 Closure ,乍看之下一定會覺得依序的執行結果會是 0,1,2 ,可是~~瑞凡,~~並不是,結果是 3,3,3 ! 這是為什麼?很簡單,我們在把函式推到陣列裡面的時候 ,因為處於 pushFuncToArray 內部且有引用到該函式內的變數 i ,而形成閉包。
JS 引擎的確會暫時為你保留 i 的記憶體空間,不過因為在把函式推送到陣列裡面的時候,並沒有立即引用到 i ,所以等到 pushFuncToArray 結束,一個一個執行 functionArr 裡面的函式時, i 早就已經被 for 迴圈修改為 3 (因為迴圈已經結束,i 維持在跳出迴圈之前的值 ) ,這時候怎麼拿,當然 i 都會是 3 啦!
之後還會講到相同的概念,如果你有點不懂,後面還會再講到,但請盡量確保一定要弄清楚再往下囉! 那麼我們明天見。