長久以來,閉包是前端同學面試必考的問題。會用閉包也成了高級前端開發者的標志,今天就來徹底弄清楚閉包的每一個細節。
1.閉包是什么?
比較官方的定義是:一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
我個人的簡化理解是:閉包就是函數及函數上下文環境的集合。上下文可以理解為函數可以訪問到的所有變量。
很多人肯定這樣寫過JavaScript代碼:
var paramA = "test";
var functionA = function(){
console.log(paramA);
}
這樣寫其實就是使用了閉包的概念,只是很多人并沒有意識到這是閉包。這段代碼值得注意的地方是函數functionA內部調用了函數外的變量paramA。這就是閉包的鮮明特征。
2.為什么會有閉包?
閉包是JavaScript鏈式作用域的副產品。閉包不是JavaScript獨有的特性,和JavaScript有類似作用域設計的語言也存在閉包,比如Python。
鏈式作用域的設計決定了子作用域中的函數可以訪問到父級作用域的變量,嵌套函數可以訪問到外部函數的變量。
借用一張圖來說明一下JavaScript的鏈式作用域。
3.閉包如何使用?
前面的小例子雖然體現了閉包的特征,但并不是一個真正的閉包函數。看下面這個例子:
function a() {
var i = 0;
function b() { console.log(i++); }
return b;
}
var c = a();
c();
這里函數b就是一個閉包函數,那為什么要用c去調用函數a呢?這是為了防止JavaScript的垃圾回收機制生效。在Javascript中,如果一個對象不再被引用,那么這個對象就會被GC回收。如果兩個對象互相引用,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收。因為函數a被b引用,a又被c引用。這樣函數a和b才不會被回收。
4.閉包有什么作用?
我認為閉包的主要作用有兩點:
1.保持變量常駐內存,不被回收。
2.實現私有的方法和屬性,禁止外部訪問。
第一點很好理解,比如上面的例子中i就會常駐內存,每次執行c函數都會得到i的值。這種情況很常見,比如游戲中的的的分數等。
第二點是閉包最重要的用處,還是以上面的例子來說。i是函數a的局部變量,如果想修改i的值,只有調用函數b。其他外部函數是無法訪問到變量i的,這樣就保證了變量i的安全。
5.閉包的缺點
事物都有兩面性,閉包在帶來方便的同時也有一些弊端。
因為閉包函數會使變量常駐內存,如果使用不當。比如在循環中使用閉包,有可能導致內存壓力過大。
在IE中會導致內存泄漏,這是IE的bug并不是閉包的問題。
6.我的觀點
我猜測閉包是JavaScript在設計之初未曾想到過的用法,如果業務中沒有強烈的需求,盡量不要使用閉包。