閉包是javascript的一個難點,也是一個重點,很多高級應用都需要用到閉包。在學習閉包的過程中看了不少書和大牛的博客,下面來說說我對閉包的理解和看法。
### 函數
要說閉包就不得不先說一下函數,這個javascript的中流砥柱。
眾所周知在ES6之前js都沒有class這個關鍵字,一切的面向對象都是通過function模擬的。
一個簡單到不能再簡單的例子:函數內部可以訪問外部的變量(全局變量)。
~~~
var n=100;
function f1()
{
console.log(n);
}
f1();//100
~~~
<br>
而外部卻不能訪問函數內的局部變量。
~~~
function f1()
{
var n=100;
}
console.log(n);//n is not defined
~~~
<br/>
那么問題來了,如果我想訪問函數內的局部變量怎么辦?一種解決辦法就是使用閉包。
什么是閉包?我個人認為函數中的函數就是閉包,有點像java的內部類,既然函數可以訪問外部變量,那么函數中的函數也可以訪問外層函數的變量,這個內層函數就是閉包。
~~~
function f1()
{
var n=100;
function f2()
{
console.log(n);
}
f2();
}
f1();//100
~~~
上述寫法比較麻煩,因此可以把f2作為一個返回值返回給f1。
~~~
function f1()
{
var n=100;
function f2()
{
console.log(n);
}
return f2;
}
f1()();//100
//相當于var f=f1();f();
~~~
<br>
下面來看一個問題:這里有5個li,名字是0到4,我希望我點擊0的時候在控制臺輸出0,點擊1的時候在控制臺輸出1,以此類推。
~~~
var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
lis[i].onclick=function()
{
console.log(i);
}
}
~~~
奇怪的事情發生了,無論我點擊哪一個在控制臺輸出的都是5。
這是為什么呢?
仔細觀察,我為每個li創建了一個匿名函數,匿名函數形成了閉包,它們都在引用外部的i,當i改變時,自然所有匿名函數里面的i都改變了。用幾張圖來說明。



當i增加到5的時候不再滿足循環條件,由于五個匿名函數都是引用同一個i,所以它們打印出來的都是5。
既然它們是引用同一個i,那么只需為每一個i創建一個備份
~~~
var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
lis[i].onclick=function(num)
{
return function()
{
console.log(num);
}
}(i)
}
~~~
這里在匿名函數接收一個參數并且立即執行,匿名函數內部又創建了一個函數負責把這個參數返回給匿名函數,由于是按值傳遞的,所以相當于給i做了一個備份。就算i一直在改變,而每個匿名函數的num是固定的。
<br>
### 閉包中的this
在閉包中使用this會出現一些問題,在全局函數中this指向的是window,當函數以方法被調用時,this就指向這個對象。但是,匿名函數的執行環境具有全局性,this一般指向window。參考紅寶石書中的一個經典的問題:
~~~
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//"The Window"
~~~
內部函數在搜索this的時候只會搜到其活動對象為止,由于匿名函數的執行環境具有全局性,因此當前的活動對象就是window。
<br>
可以通過改變當前的活動對象來改變this的指向
~~~
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());//"My Object"
~~~
這里把this對象賦值給了一個that變量,因此當前的活動對象就從window變成了object。
<br>
### 閉包引發的問題
閉包雖好,但是也引發了一系列問題,尤其是內存泄漏。js的垃圾回收機制是引用計數的,當一個變量不再使用的時候(引用計數為0)垃圾回收機制就會把它清理掉。而閉包的存在會導致引用數至少為1。
~~~
function f()
{
var myDiv=document.getElementById("Div1");
myDiv.onclick=function()
{
console.log(myDiv.id);
};
}
~~~
上述代碼中,函數f需要等myDiv清除后才能被清除,而myDiv由于匿名函數的存在,它的引用數至少為1,因此它所占用的內存永遠不會被回收。
<br>
解決辦法:
~~~
function f()
{
var myDiv=document.getElementById("Div1");
var id=myDiv.id
myDiv.onclick=function()
{
console.log(id);
};
myDiv=null;
}
~~~
把myDiv.id保存在一個變量中,并且在閉包中引用這個變量,這樣就與myDiv沒有關系了。但是閉包會引用包含函數的整個活動對象,因此必須把myDiv設為null來解除對它的引用,確保垃圾回收機制能夠把資源回收。
<br>
使用閉包應該注意的問題:閉包雖然好,但是在實際應用中盡量減少 閉包的使用,并且為了防止內存泄漏,要時刻記得在閉包完后清除它的局部變量。
- html/css
- 不一樣的css3之Transform
- 不一樣的css3之Transition
- 不一樣的css3之Animation
- Less初學
- Sass初學
- 水平垂直居中那些事
- css優先級
- css基礎教學
- javascript
- 淺談javascript事件處理程序
- cookie,localStorage,sessionStorage的區別
- Ajax
- 說說JSON
- 數組常用的方法
- 字符串常用的方法
- 閉包之我的理解
- 常用DOM操作
- 扒一扒所謂的面向對象
- JS Blob對象
- ES6學習筆記(一)
- ES6學習筆記(二)
- 用ES6書寫React
- React+Redux實戰總結
- 基于Express搭建開發環境
- 其他
- github初學
- 輕松配置Webpack
- asp.net學習筆記
- ado.net
- 如何使用ajax進行前后端交互
- 銀行大廳自助服務系統需求分析
- 西電銀行開發手冊
- 接口
- ajax