Closure
有了以上的概念之後,這樣就可以幫我們更理解 closure :
var a = 1
function test() {
var b = 2
function inner() {
console.log(a, b) // 1 2
}
return inner
}
const inner = test()
inner()
global EC => 執行 => testEC => 執行 :
剛剛有上面有解釋 EC 過了,這邊就不多講了。
那當 test 執行完之後,此時 b=2 ,然後 return inner,照理來說,這個 testEC 就會消失被回收了,但是 inner.[[Scope]] = [testEC.AO, globalEC.VO]
會被留存,因為 JS 知道我們待會會使用到。
[testEC.AO, test.[[Scope]] ] = [testEC.AO, [globalEC.VO] ] 被保留起來!
好的,回傳了 inner 之後,程式遇到 inner() => 初始化 inner EC :
好的,這邊也就解釋 closure 原理 了,由此可知如果當 b = 2 換成是 b = {超級大 object},那麼這個超級大 object 就會被保留起來,但是我們可能只是需要其中的一小部分,這樣就太浪費空間了,所以使用上要小心!
還有,我覺得老師舉的例子有深刻的讓我體會到 closure 的效率,就是當我們需要重複計算複雜的計算的時候,我們可以利用 closure 的原理更有效率的執行程式:
function complex(n) {
console.log('進入運算')
return n*n*n
}
function cache(fn){
var ans = {}
return function(n) {
if(ans[n]) {
return ans[n]
}
ans[n] = fn(n)
return ans[n]
}
}
const cachedComplex = cache(complex)
console.log(cachedComplex(10)) // 進入運算 1000
console.log(cachedComplex(10)) // 1000
console.log(cachedComplex(10)) // 1000
console.log(cachedComplex(10)) // 1000
神奇吧,把答案放入一個變數中,這樣就不用每次都要進行運算。
另外再提一個也是在 closure 中,我經常會搞到很混亂的例子:
var arr = []
for (var i =0; i< 5; i++){
arr[i] = function(){
console.log(i)
}
}
arr[0]()
我們可能會預期 arr[0]()
會 console 出 0,arr[1]()
就 console 出 1,但發現結果都是 5。
在我們學了作用域,學了 closure 之後,我們應該要很清楚的知道,為什麼答案是 5 而不是我們想的那樣。這是因為 var 的特性,我們實際上在執行 arr[0]()
的時候,此時的 i 是 global 的 i ,已經變成 5 了。
而關於此解法最簡單的就是將 var i = 0
改成 let i = 0
就可以了 。
或者是我們可以專業的使用 closure 的方式來解決:
var arr = []
for (var i =0; i< 5; i++){
arr[i] = logN(i)
}
function logN(n) {
return function(){
console.log(n)
}
}
arr[0]()
最後還有 IIFE 的方式:
var arr = []
for (var i =0; i< 5; i++){
arr[i] = (function(n){
return function(){
console.log(n)
}
})(i)
}
arr[0]()
closure 的應用
實務上使用 closure 的時候是在想要隱藏某些資訊的時候,來讓其他人不能從外部更改值。
老師舉了一個例子我覺很不錯:
var money = 99
function add(num) {
money += num
}
function deduct(num) {
if(num >=10) {
money -=10
} else {
money -=num
}
}
add(1)
deduct(100)
console.log(money) //90
看似沒問題,但是如果其他人隨便在程式放上 money = -1
那 money 就會簡簡單單的變成 -1 了。
這時候就需要 closure 去把變數封裝起來預防這種行為:
function createWallet(initMoney){
var money = initMoney
return {
add: function(num){
money +=num
},
deduct: function(num){
if(num >=10) {
money -=10
} else {
money -=num
}
},
getMoney() {
return money
}
}
}
var myWallet = createWallet(99)
myWallet .add(1)
myWallet .deduct(100)
console.log(myWallet.getMoney()) //90
這樣子改寫之後,就算有人執行 myWallet.money = -100
,myWallet 的 money 也是不會改變的。
這個 wallet 的例子中,雖然是利用 closure 的概念去實作,但是其實已經有散發出物件導向的氣味出來了。
是的,接下來就要筆記我自己覺得困難度最高的一部份,JS 的物件導向。