什么是閉包?
(function(){
var local = '你好'
function fn(){
console.log(local)
}
})()
上面的代碼就形成了一個(gè)閉包:
「函數(shù)」和「函數(shù)內(nèi)部能訪(fǎng)問(wèn)到的變量」的總和验游,就是一個(gè)閉包
所以借卧,可以這么說(shuō),JS中所有的函數(shù)都是閉包敦姻。因?yàn)镴S中所有的函數(shù)都可以訪(fǎng)問(wèn)全局變量瘾境,那么全局變量和函數(shù)就形成了閉包。
閉包不是故意弄出來(lái)的東西镰惦,而是JS函數(shù)作用域的副產(chǎn)品迷守。
函數(shù)作用域
ES 5 中只有兩種作用域,全局作用域和函數(shù)作用域旺入。函數(shù)內(nèi)部的變量可以讀取全局作用域中的變量兑凿,而函數(shù)外部不能讀取函數(shù)內(nèi)部定義的變量。而閉包眨业,就是溝通全局作用域和函數(shù)作用域之間的橋梁急膀,全局作用域和函數(shù)作用域通過(guò)閉包來(lái)實(shí)現(xiàn)變量的連接。
閉包的一般形式
常見(jiàn)的使用閉包的一般形式是使用自執(zhí)行函數(shù) 或 函數(shù)里面套函數(shù)龄捡,目的是為了提供一個(gè)函數(shù)作用域卓嫂,隔開(kāi)函數(shù)作用域和全局作用域,達(dá)到隱藏變量的目的聘殖。使用閉包將變量聲明放到函數(shù)作用域中晨雳,除了這個(gè)函數(shù)的其他地方就不能訪(fǎng)問(wèn)到這個(gè)變量,變量就隱藏了奸腺。
閉包的用途
- 讀取函數(shù)內(nèi)部變量
function foo(){
var xx = '你好'
return function(){
return xx
}
}
// 上面這段代碼中餐禁,函數(shù) foo 返回的匿名函數(shù)與 foo 中的變量 xx 形成了一個(gè)閉包。
// foo 返回一個(gè)匿名函數(shù)突照,該匿名函數(shù)讀取 foo 作用域中的變量帮非,之后返回該變量。
// 調(diào)用函數(shù) foo ,返回一個(gè)匿名函數(shù)末盔,再調(diào)用該匿名函數(shù)筑舅,就可以讀取 foo 中的變量。
var getVariable = foo()
getVariable() // 返回 '你好'
console.log(xx) // 報(bào)錯(cuò)陨舱,xx 沒(méi)有被定義
- 讓變量保持在內(nèi)存中翠拣,不被垃圾回收機(jī)制回收
function foo(xx){
return function(){
return xx++
}
}
// 上面代碼中,使用閉包游盲,多創(chuàng)建了一個(gè)匿名函數(shù)的作用域來(lái)保存變量 xx 的值误墓。
// 在函數(shù) foo 執(zhí)行之后,xx 的值在匿名函數(shù)中被保留下來(lái)益缎,不會(huì)因?yàn)闆](méi)有被引用而被內(nèi)存回收
var fn = foo(1)
fn() // 1谜慌,每一次調(diào)用 fn,其實(shí)都是在調(diào)用 fn(foo 中返回的匿名函數(shù)) 作用域中的xx
fn() // 2链峭,每一次調(diào)用 fn畦娄,其實(shí)都是在調(diào)用 fn(foo 中返回的匿名函數(shù)) 作用域中的xx
fn() // 3,每一次調(diào)用 fn弊仪,其實(shí)都是在調(diào)用 fn(foo 中返回的匿名函數(shù)) 作用域中的xx
var li = document.querySelectorAll('li')
for(var i = 0; i < li.length; i++){
li[i].onclick = function(){
console.log(i)
}
}
// 上面這段代碼,每一次點(diǎn)擊 li杖刷, 都會(huì)打印出 5
// 這是因?yàn)樯厦娴?i励饵,用的都是同一個(gè)作用域下的變量 i,這個(gè)作用域之中的 i滑燃,在經(jīng)過(guò) for 循環(huán)之后役听,值會(huì)變成 5。
// 而想要讓代碼達(dá)成我們想要的效果表窘,就需要為每一個(gè)變量 i 創(chuàng)建一個(gè)作用域典予,
// 使打印出來(lái)的每一個(gè)變量 i 的作用域不同,就是打印出不同的 i乐严,使用閉包就可以做到瘤袖。
var li = document.querySelectorAll('li')
for(var i = 0; i < li.length; i++){
li[i].onclick = (function(i){
return function(){
console.log(i)
}
})(i)
}
// 上面代碼新增了一個(gè)函數(shù)作用域,用來(lái)保存變量 i 的值昂验。新增的函數(shù)返回要執(zhí)行的回調(diào)函數(shù)捂敌,
// 又因?yàn)?‘click’事件是直接執(zhí)行的,所以既琴,要將返回的函數(shù)直接執(zhí)行占婉,使用自執(zhí)行函數(shù)的方式,來(lái)執(zhí)行返回的回調(diào)函數(shù)甫恩。
// 這時(shí)調(diào)用的回調(diào)函數(shù)逆济,每一個(gè) i 都是獨(dú)立函數(shù)作用域之中的 i,它們的值都不一樣。
- 封裝私有變量奖慌、方法
function fn(a){
var b = 'hi'
function add(){
return a++
}
function reduce(){
return a--
}
function getA(){
return a
}
function getB(){
return b
}
function modefyB(x){
b = x
return b
}
return {
getA: getA,
add: add,
reduce: reduce,
getB: getB,
modefyB: modefyB,
}
}
// 上面的代碼封裝了兩個(gè)私有變量和4個(gè)私有方法抛虫。
// 一個(gè)私有變量是參數(shù)傳入的,一個(gè)私有變量是自身定義的升薯。
// 通過(guò)私有方法來(lái)對(duì)私有變量進(jìn)行操作莱褒。
var f1 = fn(1)
var f2 = fn(10)
// f1 和 f2 是兩個(gè)不同的對(duì)象,擁有各自的私有變量和私有方法涎劈,分別對(duì) f1 和 f2 進(jìn)行操作广凸,并不會(huì)影響另一個(gè)。
f1.getA() // 1
f1.getB() // 'hi'
f1.modefyB('hello')
f1.getB() // 'hello'
f2.getA() // 10
f2.getB() // 'hi'
f2.modefyB('放心蛛枚,我不會(huì)改變對(duì)象f1的谅海。不信你試試?')
f2.getB() // '放心蹦浦,我不會(huì)改變對(duì)象f1的扭吁。不信你試試?'
f1.getB() // 'hello'