一路上感謝各位讀者們的支持和回饋。
本 30 天系列文目前已經將篇幅重新整理、編纂成冊。
《JavaScript 概念三明治》在天瓏書局上架囉!
喜歡這個系列,想閱讀更詳細原理說明的讀者可以參考:
https://www.tenlong.com.tw/products/9789864347575
函數內的變數在函式執行完之後,就無法再被參照到,這個時候一開始被分派的記憶體就會被釋放什麼意思呢?
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 funcArr
}
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 啦!
之後還會講到相同的概念,如果你有點不懂,後面還會再講到,但請盡量確保一定要弄清楚再往下囉! 那麼我們明天見。
請問第一個例子
getEnemyInfo 函式原始碼 在哪裡?
Sam 你好:
感謝回覆,應該是我當初上架時有漏掉一部分的範例程式碼,導致表達不清楚,剛才已經修改並整合進文章了,重新再跟你解釋一次,最新的範例在這邊:
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()
以這個例子我想表達的是在一個函式內宣告的變數,是無法於外部另外一個函式的作用域取得的。
但是反過來卻可以,這時候 JS 會根據程式碼的實際位置(語彙環境)與目前執行到哪一個函式(執行環境)來決定閉包會不會產生。
如果還有不了解也可以再發問,我會盡可能回答你的。再次感謝你的發問!
謝謝你,你發布的文章很受用~
function pushFuncToArray(){
var funcArr = []
for (var i=0; i<3; i++){
funcArr.push(function(){
console.log(i)
})
}
return functionArr
}
return functionArr
應該是 return funcArr
XD
jim63 謝謝你喔!不好意思筆誤,已經修正啦!
(你的眼睛也太尖了哈哈哈)