JavaScript 閉包
什么是JavaScript 閉包
函數(shù)和對(duì)其周圍狀態(tài)的引用捆綁在一起構(gòu)成閉包。也就是說(shuō),閉包可以讓你從內(nèi)部函數(shù)訪問(wèn)外部函數(shù)作用域。在 JavaScript 中,每當(dāng)函數(shù)被創(chuàng)建,就會(huì)在函數(shù)生成時(shí)生成閉包。
作用域
請(qǐng)看下面的代碼
function init(name){ ? ?// 內(nèi)部函數(shù),因此就形成了一個(gè)閉包 ? ?function show(name){ ? ? ? ?console.log('name',name); ? ?} ? ?show(name);}
init('愛上學(xué)院');
最后頁(yè)面效果如下:

init()?創(chuàng)建了一個(gè)局部變量?name?和一個(gè)名為?show()?的函數(shù)。show()?是定義在?init()?里的內(nèi)部函數(shù),并且僅在?init()?函數(shù)體內(nèi)可用。請(qǐng)注意,show()?沒(méi)有自己的局部變量。然而,因?yàn)樗梢栽L問(wèn)到外部函數(shù)的變量,所以?show()?可以使用父函數(shù)?init()?中聲明的變量?name?。
運(yùn)行該代碼后發(fā)現(xiàn),?show()?函數(shù)內(nèi)的?第4行語(yǔ)句成功執(zhí)行了。這個(gè)例子描述了分析器如何在函數(shù)嵌套的情況下解析變量名。詞法一詞指的是,詞法作用域根據(jù)源代碼中聲明變量的位置來(lái)確定該變量在何處可用。嵌套函數(shù)可訪問(wèn)聲明于它們外部作用域的變量。
下面我們來(lái)看第二個(gè)例子:
function sum(x) { ? ?return function sum1(y) { ? ? ? ?return x + y; ? ?}}
document.write(sum(2)(5));
執(zhí)行上面代碼,最后會(huì)在頁(yè)面顯示“7”,如下圖:

實(shí)用的閉包
閉包很有用,因?yàn)樗试S將函數(shù)與其所操作的某些數(shù)據(jù)(環(huán)境)關(guān)聯(lián)起來(lái)。這顯然類似于面向?qū)ο缶幊獭T诿嫦驅(qū)ο缶幊讨?,?duì)象允許我們將某些數(shù)據(jù)(對(duì)象的屬性)與一個(gè)或者多個(gè)方法相關(guān)聯(lián)。
因此,通常你使用只有一個(gè)方法的對(duì)象的地方,都可以使用閉包。
在 Web 中,你想要這樣做的情況特別常見。大部分我們所寫的?JavaScript 代碼都是基于事件的 — 定義某種行為,然后將其添加到用戶觸發(fā)的事件之上(比如點(diǎn)擊或者按鍵)。我們的代碼通常作為回調(diào):為響應(yīng)事件而執(zhí)行的函數(shù)。
我們來(lái)看下面一個(gè)例子:點(diǎn)擊按鈕改變文字的字體大小。代碼(只展示js代碼)和頁(yè)面效果如下:

上面的代碼,是我們通常的寫法。下面我們就采用閉包的形式來(lái)寫。
采用閉包形式改寫的代碼如下:
function changeFontSize(fontSize) { ? ?return function () { ? ? ? ?document.getElementById("msg").style.fontSize = `${fontSize}px`; ? ?}}const changeFontSize18 = changeFontSize(18);const changeFontSize24 = changeFontSize(24);const changeFontSize32 = changeFontSize(32);document.getElementById("btn18").onclick = changeFontSize18;document.getElementById("btn24").onclick = changeFontSize24;document.getElementById("btn32").onclick = changeFontSize32;
私有方法
編程語(yǔ)言中,比如 Java,是支持將方法聲明為私有的,即它們只能被同一個(gè)類中的其它方法所調(diào)用。
而 JavaScript 沒(méi)有這種原生支持,但我們可以使用閉包來(lái)模擬私有方法。私有方法不僅僅有利于限制對(duì)代碼的訪問(wèn):還提供了管理全局命名空間的強(qiáng)大能力,避免非核心的方法弄亂了代碼的公共接口部分。
下面的示例展現(xiàn)了如何使用閉包來(lái)定義公共函數(shù),并令其可以訪問(wèn)私有函數(shù)和變量。這個(gè)方式也稱為模塊模式
const Count = (function () { ? ?// 定一個(gè)變量 ? ?let count = 0;
? ?// 定一個(gè)調(diào)整count的方法,該方法只能在這里使用 ? ?function change(val) { ? ? ? ?count += val; ? ?}
? ?return { ? ? ? ?add: function (step) { ? ? ? ? ? ?change(step); ? ? ? ?}, ? ? ? ?get: function () { ? ? ? ? ? ?return count; ? ? ? ?} ? ?}})();// 獲取count的值console.log(Count.get());// 0// count增加1Count.add(1)// count增加20Count.add(20)// 獲取count的值console.log(Count.get());// 21// count值減小3Count.add(-3)// 獲取count的值console.log(Count.get());// 18
在之前的示例中,每個(gè)閉包都有它自己的語(yǔ)法環(huán)境;而這次我們只創(chuàng)建了一個(gè)語(yǔ)法環(huán)境,為兩個(gè)函數(shù)所共享:Count.add和?Count.get。
該共享環(huán)境創(chuàng)建于一個(gè)立即執(zhí)行的匿名函數(shù)體內(nèi)。這個(gè)環(huán)境中包含兩個(gè)私有項(xiàng):名為?count?的變量和名為?change?的函數(shù)。這兩項(xiàng)都無(wú)法在這個(gè)匿名函數(shù)外部直接訪問(wèn)。必須通過(guò)匿名函數(shù)返回的兩個(gè)公共函數(shù)訪問(wèn)。
這兩個(gè)公共函數(shù)是共享同一個(gè)環(huán)境的閉包。多虧 JavaScript 的語(yǔ)法作用域,它們都可以訪問(wèn)?count?變量和?change?函數(shù)。
下面我們來(lái)把上面的代碼做一下微調(diào):創(chuàng)建兩個(gè)Count。
const Count = function () { ? ?// 定一個(gè)變量 ? ?let count = 0;
? ?// 定一個(gè)調(diào)整count的方法,該方法只能在這里使用 ? ?function change(val) { ? ? ? ?count += val; ? ?}
? ?return { ? ? ? ?add: function (step) { ? ? ? ? ? ?change(step); ? ? ? ?}, ? ? ? ?get: function () { ? ? ? ? ? ?return count; ? ? ? ?} ? ?}};const Count1 = Count();const Count2 = Count();
// 獲取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 獲取Count2里面的count值console.log("Count2里面的count:", Count2.get());// Count1里面的count增加1Count1.add(1)console.log("==========Count1里面的count增加1================");// 獲取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 獲取Count2里面的count值console.log("Count2里面的count:", Count2.get());// Count2里面的count增加1Count2.add(13)console.log("==========Count2里面的count增加13================");// 獲取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 獲取Count2里面的count值console.log("Count2里面的count:", Count2.get());
運(yùn)行結(jié)果如下:

我們會(huì)發(fā)現(xiàn)兩個(gè)計(jì)數(shù)器?Count1?和?Count2?,每個(gè)閉包都是引用自己詞法作用域內(nèi)的變量?count?。
每次調(diào)用其中一個(gè)計(jì)數(shù)器時(shí),通過(guò)改變這個(gè)變量的值,會(huì)改變這個(gè)閉包的詞法環(huán)境。然而在一個(gè)閉包內(nèi)對(duì)變量的修改,不會(huì)影響到另外一個(gè)閉包中的變量。