閉包,一個完全無法從字面意思了解的專有名詞,若是改叫小籠閉包,是不是馬上聯想到這個畫面
一個個湯包被安放在蒸籠中,呼應到閉包在程式碼中的封閉區塊概念,是不是具象化許多呢
讓我們從以下的舉例一步步來了解為什麼要有閉包的設計
let cookie = 1;
function addCookie(){
cookie++;
}
addCookie();
addCookie();
console.log(cookie) // 2
在全域下宣告一個值為 1 的變數 cookie及宣告一個 addCookie function,每次呼叫 cookie 值增加 1
若希望 cookie 的值 +2,呼叫兩次 addCookie,果然這時 cookie 的值增加為 3
但有個問題來了,cookie 因為存在全域變數下,有極大的風險容易被修改,若有另外的變數也叫 cookie,但起算值想從 10 開始不就撞車了?
既然變數放全域不安全,那將 cookie 宣告放到 addCookie 內試試看
function addCookie(){
let cookie = 1;
cookie++
console.log(cookie);
}
addCookie(); // 2
addCookie(); // 2
稍微修改一下,變數宣告放到函式內、cookie++ 後再 return 拋出,
一樣進行兩次呼叫,結果結果的值都是 2 ?!
雖然 cookie 變數被好好的存在 addCookie 中,但每次呼叫 cookie 都重新被賦值 1,所以不管今天呼叫 addCookie 幾次得到的值都只會是 1+1 = 2 的結果
那怎麼做到每次呼叫 cookie +1 ,變數又不會被隨意修改呢?
透過 閉包 Closure 就對啦
諸如 Java 之類的程式語言,提供了私有方法宣告的能力,意味著它們只能被同一個 class 的其他方法呼叫。
JavaScript 並沒有的提供原生的方法完成這種事,不過它藉由閉包來模擬私有方法。私有方法不只能限制程式碼的存取,它還提供了強而有力的方式來管理全域命名空間,避免非必要的方法弄亂公開介面。 - MDN
閉包的標準起手式就是之前提過的 巢狀結構 Nested function,在 function 內 return function
function addCookie(){
let cookie = 1;
return function (){
cookie++
console.log(cookie);
}
}
let cookieNumber = addCookie();
cookieNumber() // 呼叫匿名函式,cookie++ 變成 2
cookieNumber() // 再次呼叫匿名函式,cookie++ 變成 3
將 cookie + 1 的動作包在內層的匿名函式內,在外層將 addCookie 的呼叫結果存入變數 cookieNumber 中
存入 cookieNumber 的是什麼?就是閉包中的匿名函式
所以呼叫 cookieNumber 等同於執行匿名函式,得到 cookie + 1 的結果
那第二次呼叫 cookieNumber 呢? 為什麼是取得 3
記得前幾天講過的 字彙環境 和 Scope chain 嗎
匿名函式中並沒有記錄 cookie 的值,需要往外一層到 addCookie 的字彙環境查找,此時 cookie 的值因為上一次的呼叫被重新賦予值為 2,所以再加 1 的結果就是 3 啦
那如果沒有將內部的匿名函式存入一個變數,而是用 cookieNumber( )( ) 的方式執行匿名函式呢?
function addCookie(){
let cookie = 1;
return function (){
cookie++
return cookie;
}
}
// 存入變數
let cookieNumber = addCookie();
cookieNumber()// 2
cookieNumber()// 3
// 使用()()
addCookie()() // 2
addCookie()() // 2
想當初又傻又天真的我以為使用兩個小括號的意思就跟存入變數後再呼叫一樣,直到被饅頭大大敲醒
如果你也跟當初的我有一樣的困惑,沒關係,加上一個 console.log 你就懂了
function addCookie(){
let cookie = 1;
console.log('you have to pass me first!')
return function (){
cookie++
return cookie;
}
}
addCookie()();
// you have to pass me first!
// 2
You have to pass me first !
記得昨天提到的使用 node 指令印出 bytecode 嗎?
輸入 node --print-bytecode --print-bytecode-filter=addCookie test.js
後印出 addCookie function 的 bytecode,發現其中一行的內容顯示為 createClosure
!
對應上 closure 不僅是理論上的名詞定義,在 V8 內部的實作內也確實區分出 closure 特性的操作
學習過 Lexical Environment、Scope Chain 和 Execution Context 後看閉包能更快上手,一切的一切都關乎作用域的概念呀
忍者JavaScript 開發技巧探秘2 by John Resig、Bear Bibeault、Josip Maras
所有的函式都是閉包:談 JS 中的作用域與 Closure Huli 大大在詳盡的 closure 文章
MDN - 閉包
W3School
I never understood JavaScript closures
你懂 JavaScript 嗎?#15 閉包(Closure)