JS 原力覺醒 Day08 - Closures

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 函式。 這個函式會返回另外一個函式,之後才會真正把兩個數字加起來,在我們輸入第一個參數之後,就會結束該函式執行環境並返回另外一個函式。

https://ithelp.ithome.com.tw/upload/images/20190923/201065801yPoLwCGrd.jpg

照理說當 addA 函式回傳第二個函式之後,addA 的執行環境就結束,就沒辦法拿到該函式參數 numA ,但對 addB 函式而言,在其內部有引用到他內部沒有的變數 (numA),因此他會轉為向外部環境( Scpoe Chain )尋找 ,JS 引擎就會為此保留這個函式的記憶體空間,不會釋放。

https://ithelp.ithome.com.tw/upload/images/20190923/20106580IF1A2kWGjV.jpg

這看起來就 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 ,而形成閉包。

https://ithelp.ithome.com.tw/upload/images/20190923/20106580AWlAh7veMH.jpg

JS 引擎的確會暫時為你保留 i 的記憶體空間,不過因為在把函式推送到陣列裡面的時候,並沒有立即引用到 i ,所以等到 pushFuncToArray 結束,一個一個執行 functionArr 裡面的函式時, i 早就已經被 for 迴圈修改為 3 (因為迴圈已經結束,i 維持在跳出迴圈之前的值 ) ,這時候怎麼拿,當然 i 都會是 3 啦!

之後還會講到相同的概念,如果你有點不懂,後面還會再講到,但請盡量確保一定要弄清楚再往下囉! 那麼我們明天見。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×