閉包
- 使用chrome調(diào)試查看
- 閉包是有權(quán)訪問另一個函數(shù)作用域的變量的函數(shù).
簡而言之,這些函數(shù)表達式定義在另一個函數(shù)的函數(shù)體內(nèi),它可以訪問它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)和聲明的其他內(nèi)部函數(shù)。當其中一個這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時,就會形成閉包,例如:
function fn1() {
var num = 0
function fn2() { //fn1函數(shù)內(nèi)嵌套著內(nèi)部函數(shù)fn2
num++
console.log(num)
}
return fn2 //將fn2返回
}
var result = fn1() //相當于result = function fn2() {num++;console.log(num);}
result() //1
在上面的例子中fn2就是閉包。
產(chǎn)生閉包的條件
- 函數(shù)嵌套
- 內(nèi)部函數(shù)引用了外部函數(shù)的數(shù)據(jù)(變量/函數(shù))
- 執(zhí)行外函數(shù)
閉包的生命周期
- 產(chǎn)生: 在嵌套內(nèi)部函數(shù)定義執(zhí)行完時就產(chǎn)生了(不是調(diào)用)
- 死亡: 在嵌套的內(nèi)部函數(shù)成為垃圾對象時
閉包的用途
-
可以讀取函數(shù)內(nèi)部的變量。
- 根據(jù)作用域鏈定義可知子對象會一級一級地向上尋找所有父對象的變量,所以父對象的所有變量,對子對象都是可見的。上面的例子中,fn2函數(shù)就可以訪問fn1函數(shù)內(nèi)部的局部變量num
- 平時我們在函數(shù)中的返回值經(jīng)常是一個值,既然fn2函數(shù)能夠獲取fn1內(nèi)部的變量,我們將fn2函數(shù)返回就能在外部獲取num變量了
- 將fn2返回后,相當于
var result = function fn2() {num++;console.log(num);},即是外部的函數(shù)result也能獲取局部變量num了
讓變量的值始終保存在內(nèi)存中。
function fn1() {
var num = 0
function fn2() {
num++
console.log('閉包函數(shù)' + num)
}
return fn2
}
var result = fn1()
result() //閉包函數(shù)1
result() //閉包函數(shù)2
function commonFn() {
var num = 0
num++
console.log('普通函數(shù)' + num)
}
commonFn() //普通函數(shù)1
commonFn() //普通函數(shù)1
從上面的例子中可知,重復(fù)調(diào)用閉包函數(shù)時,每次輸出的num值都會+1,而普通函數(shù)重復(fù)執(zhí)行后num值仍然保持不變,這證明了函數(shù)fn1中的局部變量num一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動清除。
原因:fn1是fn2的父函數(shù),fn2被賦值給全局變量result,這導致fn2始終在內(nèi)存中,而fn2依賴于fn1,故fn1的變量值一直存在于內(nèi)存中。
閉包的缺點
由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大而會導致內(nèi)存泄漏,所以在調(diào)用結(jié)束之后要將不使用的局部變量刪除掉。
內(nèi)存溢出
- 一種程序運行出現(xiàn)的錯誤
- 當程序運行需要的內(nèi)存超過了剩余的內(nèi)存時,就拋出內(nèi)存溢出的錯誤
內(nèi)存泄漏
- 占用的內(nèi)存沒有及時釋放
- 內(nèi)存泄漏積累多了就容易導致內(nèi)存溢出
- 常見的內(nèi)存泄漏
- 意外的全局變量
- 沒有及時清理的計時器或回調(diào)函數(shù)
- 閉包
閉包的this指向問題
let obj = {
name: 'Joe',
getName: function() {
return function() {
console.log(this.name)
}
}
}
obj.getName()() //<empty string>
返回的函數(shù)最終是在全局作用域中,故無法訪問到obj對象中的name屬性。
閉包的例子
- 創(chuàng)建函數(shù)工廠
function addFunc(x) {
return function(y) {
return x + y
}
}
var add5 = addFunc(5)
var add10 = addFunc(10)
console.log(add5(2)) //7
console.log(add10(2)) //12
以上例子是創(chuàng)建兩個數(shù)值相加的函數(shù)工廠,var add5 = addFunc(5)這行代碼先給x傳值5,且這行代碼相當于var add5 = function(y) {return 5 + y}在打印行再給y傳值2.
- 返回一個對象的寫法
let counter = function() {
let currentVal = 0
function addVal(value) {
currentVal += value
}
return {
increment: function() {
addVal(1)
},
decrement: function() {
addVal(-1)
},
value: function() {
return currentVal
}
}
}
let counter1 = counter()
let counter2 = counter()
console.log(counter1.value()) //0
counter1.increment()
counter1.increment()
console.log(counter1.value()) //2
counter1.decrement()
console.log(counter1.value()) //1
console.log(counter2.value()) //0
由這個例子可知counter1和counter2是獨立的,互相不影響的,也就是它們的值是私有的。