閉包是Js的一個(gè)難點(diǎn),也是它的一個(gè)特色,很多高級應(yīng)用都要靠閉包來實(shí)現(xiàn)。
1.變量的作用域
要理解閉包,首先必須要理解Js特殊的變量作用域。
變量的作用域無非只有2種,全局變量
和局部變量
JavaScript語言的特殊之處還在于,函數(shù)內(nèi)部可以直接讀取全局變量。
// 函數(shù)內(nèi)部可以直接讀取全局變量
var n=100
function f1(){
console.log(n)
}
f1() // 100
上面代碼中,函數(shù)f1的內(nèi)部,是可以直接讀取全局變量n的。
另一方面,函數(shù)外部自然不能讀取函數(shù)內(nèi)部的局部變量。
// 函數(shù)外部不可以讀取函數(shù)內(nèi)部變量
function f1(){
var n=100
}
f1()
console.log(n) // Uncaught ReferenceError: n is not defined
如上面代碼,在函數(shù)f1外部去嘗試打印n的時(shí)候,就報(bào)錯(cuò)。
那么如何從函數(shù)外部去讀取函數(shù)內(nèi)部的變量呢?
出于種種原因,有時(shí)候我們需要從函數(shù)外部去讀取函數(shù)內(nèi)部的變量。 但是前面說過了,正常情況下,這是辦不到的,那么就要使用變通的方法。
那就是在函數(shù)內(nèi)部,再定義一個(gè)函數(shù),并將其作為返回值
// 函數(shù)作為返回值
function f1(){
var n=100
return function bar(x){
if(x>n){
console.log(n)
}
}
}
var f=f1()
f(102) // 100
上面的代碼中,我就打印出了函數(shù)內(nèi)部變量n
2.閉包的概念
上面代碼中的bar函數(shù),就是閉包。
閉包實(shí)際上就是指閉包函數(shù),它是一個(gè)函數(shù)。
閉包實(shí)際上就是
能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)
切記,閉包是一個(gè)函數(shù)
3.閉包的用途
閉包可以用在很多方面。它最大的用處有2個(gè)。
讀取其它函數(shù)內(nèi)部變量
和 讓這些變量始終保持在內(nèi)存中
請看下面的代碼
// 函數(shù)作為返回值
function f1(){
var n=100
nAdd=function(){
n++
}
return function bar(){
console.log(n)
}
}
var f=f1()
f() // 100
nAdd()
f() // 101
上面代碼中,f實(shí)際上就是閉包函數(shù)bar
它一共運(yùn)行了2次,第一次的結(jié)果是100,第二次的結(jié)果是101。
這證明了函數(shù)f1中的變量一直保存在內(nèi)存中。并沒有在f1的調(diào)用結(jié)束后被清除
。
為什么會這樣呢? 原因就在于,f1是bar的父函數(shù),而子函數(shù)bar被賦值給了一個(gè)全新的全局變量f
,而且bar的存在依賴于f1,因此f1
也始終存在于內(nèi)存中,不會因?yàn)閒1的調(diào)用完成而被垃圾回收機(jī)制銷毀
。
這段代碼另一個(gè)值得注意的地方就在于nAdd=function(){n++}
這段,首先,nAdd
前面沒有使用var
關(guān)鍵字,、因此nAdd是一個(gè)全局變量,而不是局部變量。 你看nAdd里面也能讀到其它函數(shù)內(nèi)部的變量n
,因此nAdd也是一個(gè)閉包函數(shù)。
所以nAdd相當(dāng)于一個(gè)setter,可以在函數(shù)外部對函數(shù)內(nèi)部的變量進(jìn)行操控。
4.使用閉包注意點(diǎn)
-
由于使用閉包的使用會使得函數(shù)中定義的變量都保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包。否則會造成網(wǎng)頁性能問題。
解決辦法,是在退出函數(shù)之前,將不使用的局部變量全部刪除。
- 閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。 所以如果你把父函數(shù)當(dāng)做對象(object)來使用,把閉包當(dāng)作它的共有方法來使用,把內(nèi)部變量當(dāng)作它的私有屬性來使用,這時(shí)一定要小心,不要隨便改變父函數(shù)內(nèi)部的值。
5.例子
// 函數(shù)作為返回值
var name="The Window"
var object={
name:"My object",
getNameFunc:function(){
console.log(this) // {name:"My object",getNameFunc:f}
return function(){
console.log(this) // Window
return this.name
}
}
}
console.log(object.getNameFunc()()) // The Window
上面代碼中,this作為對象object的一個(gè)屬性被調(diào)用時(shí),指向是object這個(gè)對象,但是再return一個(gè)函數(shù)的時(shí)候,this的指向就變成了全局window
函數(shù)的作用域是創(chuàng)建的時(shí)候確定的,不是在調(diào)用的時(shí)候,最內(nèi)層那個(gè)函數(shù)創(chuàng)建的時(shí)候就是全局。 所以this指的是全局window
6. 請看下面這段代碼
// 函數(shù)作為返回值
function a(){
var i=0;
return b=()=>{
console.log(++i)
}
}
var c=a()
c() // 1
上面代碼有2個(gè)特點(diǎn):
1.函數(shù)b嵌套在函數(shù)a內(nèi)
2.函數(shù)a返回函數(shù)b
引用關(guān)系如圖
這樣在執(zhí)行完 var c=a()后,變量c實(shí)際上是指向了函數(shù)b,再執(zhí)行c()就會打印i的值。
上面的代碼實(shí)際上就創(chuàng)建了一個(gè)閉包。
因?yàn)楹瘮?shù)a外的變量c引用了函數(shù)a內(nèi)部的函數(shù)b
就是說,
當(dāng)函數(shù)a的內(nèi)部函數(shù)b被函數(shù)a外的一個(gè)變量引用的時(shí)候,就創(chuàng)建了一個(gè)閉包