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