當函數(shù)可以記住并訪問所在的詞法作用域時朝聋,就產(chǎn)生了閉包,即使函數(shù)是在當前詞法作用域之外執(zhí)行囤躁。
—— 你不知道的JavaScript(上卷)
一冀痕、閉包的定義
1、閉包的構(gòu)成
首先狸演,閉包由兩部分構(gòu)成:函數(shù)言蛇、創(chuàng)建該函數(shù)的環(huán)境,環(huán)境由閉包創(chuàng)建時在作用域中的任何局部變量組成宵距。
JS的變量作用域
變量的作用域有兩種:全局變量和局部變量腊尚。
函數(shù)內(nèi)部可以直接讀取全局變量,但是在函數(shù)外部無法讀取函數(shù)內(nèi)部的局部變量满哪。
那么婿斥,如何從外部讀取函數(shù)內(nèi)部的局部變量?在下面的代碼中哨鸭,函數(shù)f2就被包括在函數(shù)f1內(nèi)部民宿,這時f1內(nèi)部的所有局部變量,對f2都是可見的像鸡。但是反過來就不行勘高,f2內(nèi)部的局部變量,對f1就是不可見的。
這就是Javascript語言特有的 鏈式作用域 結(jié)構(gòu)(chain scope):子對象會一級一級地向上尋找所有父對象的變量华望。所以蕊蝗,父對象的所有變量,對子對象都是可見的赖舟,反之則不成立蓬戚。
既然f2可以讀取f1中的局部變量,那么只要把f2作為返回值宾抓,就可以在f1外部讀取f1的內(nèi)部變量子漩。
function f1(){
var n=999;
function f2(){
alert(n);
}
}
var test = f1();// 外部函數(shù)調(diào)用之后其變量對象n本應(yīng)該被銷毀
test();// alert 999,但閉包阻止了它們的銷毀石洗,所以仍然可以訪問外部函數(shù)的變量對象
上面代碼中的f2函數(shù)幢泼,就是閉包。
在JS中讲衫,只有函數(shù)內(nèi)部的成員才能讀取局部變量缕棵。所以在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來的橋梁涉兽。
二招驴、閉包的特性
在JavaScript中,外部函數(shù)調(diào)用之后其變量對象本應(yīng)該被銷毀枷畏,但閉包阻止了它們的銷毀别厘,所以仍然可以訪問外部函數(shù)的變量對象。
function addCalculator (x) {
return function (y) {
return x + y;
}
}
var add1 = addCalculator(1);
console.log(add1(1)); //2
add1 = null;// 釋放對閉包的引用
console.log(add1(1)); //Uncaught TypeError: add1 is not a function
通常情況下拥诡,函數(shù)的作用域及其所有變量都會在函數(shù)執(zhí)行結(jié)束后被銷毀触趴。
但如果創(chuàng)建了一個閉包的話,這個函數(shù)的作用域就會一直保存到閉包不存在為止渴肉。
閉包的特性一般為以下幾點:
1冗懦、閉包一般為外部函數(shù)嵌套的內(nèi)部函數(shù)、以及創(chuàng)建該內(nèi)部函數(shù)的環(huán)境組成的宾娜;
2批狐、閉包可以引用外部函數(shù)的參數(shù)和變量扇售,即可以訪問外部的環(huán)境前塔;
3、閉包未被釋放回收的情況下承冰,若閉包中引用了外部函數(shù)的參數(shù)和變量华弓,即使外部環(huán)境已經(jīng)被釋放回收,但是外部函數(shù)的參數(shù)和變量依然不會被垃圾回收機制回收困乒。
三寂屏、閉包的作用
1、通過閉包來模擬私有方法
私有方法有利于限制對代碼的訪問,而且可以避免非核心的方法干擾代碼的公共接口迁霎,減少全局污染吱抚。
下面的示例展現(xiàn)了如何使用閉包來定義公共函數(shù),并令其可以訪問私有函數(shù)和變量考廉。這個方式也稱為模塊模式(module pattern)
var calculator = (function(){
var a = 1;
function addCalculator(val){
a += val
}
return {
add1:function() {
addCalculator(1);
},
add2:function() {
addCalculator(2);
},
result:function() {
return a
}
}
})();
console.log(calculator.result()); // 1
calculator.add1();
console.log(calculator.result()); // 2
calculator.add2();
console.log(calculator.result()); // 4
在之前的示例中秘豹,每個閉包都有它自己的詞法環(huán)境。而這次例子中只創(chuàng)建了一個詞法環(huán)境昌粤,為三個函數(shù)所共享:calculator.add1既绕,calculator.add2和 calculator.result。
該共享環(huán)境創(chuàng)建于一個立即執(zhí)行的匿名函數(shù)體內(nèi)涮坐。這個環(huán)境中包含兩個私有項:名為 a的變量和名為 addCalculator的函數(shù)凄贩,這兩項都無法在這個匿名函數(shù)外部直接訪問,必須通過匿名函數(shù)返回的三個公共函數(shù)訪問袱讹。
這三個公共函數(shù)是共享同一個環(huán)境的閉包疲扎,它們都可以訪問 a變量和 addCalculator函數(shù)。
四廓译、閉包的優(yōu)缺點
1评肆、優(yōu)點
- 保護函數(shù)內(nèi)的變量安全 ,實現(xiàn)封裝非区,防止變量流入其他環(huán)境發(fā)生命名沖突
- 在內(nèi)存中維持一個變量瓜挽,可以做緩存(但使用多了同時也是一項缺點,消耗內(nèi)存)
- 匿名立即執(zhí)行函數(shù)可以減少內(nèi)存消耗
2征绸、缺點
- 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中久橙,內(nèi)存消耗很大,所以濫用閉包的話會造成網(wǎng)頁的性能問題管怠,在IE(IE9)之前可能導(dǎo)致內(nèi)存泄露淆衷。
解決方法:在退出函數(shù)之前,將不使用的局部變量全部刪除(例如手動賦值為null) - 由于閉包涉及跨域訪問渤弛,所以會導(dǎo)致性能損失祝拯。
解決方法:可以通過把跨作用域變量存儲在局部變量中,然后直接訪問局部變量她肯,來減輕對執(zhí)行速度的影響佳头。
引申:為何閉包的不當使用會在IE(IE9)之前可能導(dǎo)致內(nèi)存泄漏,即無法回收變量的問題晴氨?
因為IE(IE9)之前的JavaScript引擎使用的垃圾回收算法是引用計數(shù)法康嘉,對于循環(huán)引用將會導(dǎo)致GC(Garbage Collection)無法回收垃圾。
注:在后面的章節(jié)中籽前,我會找機會繼續(xù)討論什么是GC(Garbage Collection)
PS:更新來啦O(∩_∩)O亭珍,下一篇文章講到了GC(Garbage Collection):JS的垃圾回收機制