ps:如無說明款侵,下文中的“書”特指JavaScript高級程序設計這本書。
閉包:JavaScript高級程序設計這本書里,針對閉包總結了一句話,這句話是:閉包是指有權訪問另一個函數(shù)作用域中的變量的函數(shù)棘钞。
這應該就是閉包最基本的概念,要想理解閉包资溃,就必須先理解這句話武翎,而理解這句話烈炭,就必須先搞清楚這句話中的幾個重要的點溶锭。
在這句話中,有這樣幾個名詞符隙,變量趴捅、函數(shù)、函數(shù)作用域霹疫。咱們就先搞清楚這幾個名詞是什么意思拱绑。還是翻JavaScript高級程序設計這本書
1、變量:ECMAScript中的變量是松散類型的丽蝎,所為松散類型就是可以用來保存任何類型的數(shù)據猎拨。換句話說,每個變量僅僅是一個用于保存值的占位符而已屠阻。
2红省、函數(shù):函數(shù)是一個核心概念,通過函數(shù)可以封裝任意多條語句国觉,而且可以在任何地方吧恃、任何時候調用執(zhí)行。ECMAScript中的函數(shù)使用function關鍵字來聲明麻诀,后跟一組參數(shù)以及函數(shù)體痕寓。
3傲醉、函數(shù)作用域:書中對于作用域的描述有很多,都寫出來會有更多名詞需要理解呻率。這里只簡單說一說對理解閉包有用的東西硬毕。函數(shù)作用域是指在一個函數(shù)中,你聲明的變量只能在這個函數(shù)里面訪問到筷凤,比如:
function a(){
? ? var b = 0;
? ? console.log(b);
}
a(); //輸出0
console.log(b); //報錯昭殉,b is not defined
function c(){
? ? console.log(b);
}
c(); //報錯,b is not defined
上面的例子告訴我們藐守,在一個函數(shù)里面定義的變量挪丢,在函數(shù)外面是無法訪問的,在另一個函數(shù)里面卢厂,也是無法訪問的乾蓬。
但是書中對閉包的描述是,有權訪問另一個函數(shù)作用域中的變量的函數(shù)慎恒,就是閉包任内。那要怎么做才能讓一個函數(shù)訪問另一個函數(shù)的作用域中的變量呢?
書中說的方法是:創(chuàng)建閉包的常見方式融柬,就是在一個函數(shù)內部創(chuàng)建另一個函數(shù)死嗦。咱們來試一試。
首先創(chuàng)建一個函數(shù)
function a(){
? ? //聲明一個變量
? ? var b = 0;
? ? //在函數(shù)a()內部再創(chuàng)建一個函數(shù)
? ? return function(){
? ? ? ?console.log(b);
? ? }
}
var d = a();
var result = d(); //輸出0粒氧,這樣我們就訪問到了在函數(shù)a()里聲明的變量b
如果要徹底說明白越除,就要引入更多的名詞,比如變量對象外盯、執(zhí)行環(huán)境摘盆、作用域鏈等等。很復雜饱苟,也不一定非要掌握孩擂,但是理解了這些對提升水平有很大幫助。在這里都說明白的話要很多篇幅箱熬。這里我只簡單說一下我的理解类垦。
在這個例子中,函數(shù)a的作用域鏈包含兩個作用域城须,一個是全局作用域蚤认,一個是函數(shù)a()的局部作用域,在函數(shù)a()執(zhí)行完畢后酿傍,函數(shù)a()的局部作用域就被銷毀了烙懦,內存中只保留全局作用域。但是,函數(shù)a()在執(zhí)行的時候還返回了一個匿名函數(shù)氯析,這個匿名函數(shù)的作用域鏈包含了三個作用域亏较,從先后順序來說的話,即:匿名函數(shù)本身的作用域掩缓,函數(shù)a()的作用域雪情,以及全局作用域,在匿名函數(shù)執(zhí)行的時候你辣,先搜索本身的作用域巡通,沒有發(fā)現(xiàn)變量b,然后搜索函數(shù)a()的作用域舍哄,發(fā)現(xiàn)了變量b宴凉,把變量b打印出來,不再搜索全局作用域表悬。如果在函數(shù)a()里沒有找到變量b的話弥锄,就會繼續(xù)搜索全局作用域,如果找到變量b則打印蟆沫,沒有找到則報錯籽暇,b is not defined. 匿名函數(shù)一直在引用著函數(shù)a()的作用域,所以函數(shù)a()的作用域鏈不會被銷毀饭庞,它的作用域也就一直存在內存中戒悠。因此我們需要手動解除引用,銷毀函數(shù)a()的作用域鏈舟山,釋放內存绸狐。也由于閉包會攜帶包含它的函數(shù)的作用域,因此會比其他函數(shù)占用更多的內存捏顺。過度使用閉包會導致內存占用過多六孵,所以盡量在必要的時候才使用閉包纬黎。
d = null;
d(); //報錯幅骄,b is not defined