三點注意事項
- JS沒有塊級作用域,只有全局作用域和局部作用域(函數作用域)。
- JS中的作用域鏈,內部的作用域可以訪問到外部作用域中的變量和方法,而外部作用域不能訪問內部作用域的變量和方法。
- 當前作用域沒有此變量或方法,會向外部作用域尋找變量或方法。
閉包的兩種使用場景
函數作為返回值
function f() {
var a = 100;
return function () {
console.log(a);
}
}
var fn = f();
var a = 200;
fn(); // 輸出100
調用fn函數,輸出a的值,fn中並沒有定義a,所以會向上找a,在f函數的作用域中,有a,值為100。所以就會輸出100,並不會輸出200。全局作用域中的a和f函數作用域中的a並不相同。這也體現出了閉包的一個好處:不會造成全局變量污染。
函數作為參數
function f() {
var a = 100;
return function () {
console.log(a);
}
}
var fn = f();
function f2(fn) {
var a = 200;
fn();
}
f2(fn);// 輸出100
調用f2函數,傳入fn函數,調用fn函數,輸出a為100。
關於for循環和閉包之間的關系
var arr = [];
var i;
for (i = 0; i < 3; i++) {
arr[i] = function () {
return i;
}
}
console.log(arr[0]()); // 輸出2
arr[0]()的結果,按照一般的思路來講,應該是0才對,為什么是2呢?
- for循環3次,i的值從0變為2,arr這個數組中也添加了3個函數。
- 當調用
arr[0]函數時,for循環已經結束了,這時候i的值已經為2了,所以arr[0]函數取到的值為2。
如何解決這個問題?
var arr = [];
var i;
for (i = 0; i < 3; i++) {
arr[i] = (function (i) {
return function () {
return i;
}
})(i);
}
console.log(arr[0]()); // 輸出0
這里,使用自執行匿名函數構造成一個獨立作用域。每一次for循環的時候,都會執行這個匿名函數,並生成 一個匿名函數作用域。
比如第一次循環的時候,i = 0,將i作為參數傳入匿名函數中,這樣i的值就被保存在匿名函數作用域中。當調用arr[0]函數時,arr[0]就會取到匿名函數中的i的值。
閉包的實際應用
// 閉包實際應用中主要是封裝變量,收斂權限
function isFirstLoad() {
var _list = [];
return function (id) {
if (_list.indexOf(id) > 0) {
return false;
} else {
_list.push(id);
return true;
}
}
}
var firstLoad = isFirstLoad();
firstLoad(1); // 返回true
firstLoad(1); // 返回false
firstLoad(2); // 返回true
閉包的缺點
目前主流瀏覽器采用的垃圾收集策略均為標記清除。當變量進入環境(比如,定義一個變量)時,就將這個變量標記為‘進入環境’,當變量離開環境時,就會被標記為‘離開環境’,就會被銷毀。在剛才的閉包實際應用中_list變量一直被isFirstLoad的返回函數引用着,不會隨着isFirstLoad的調用結束而銷毀。所以_list變量會一直存在內存中。因此不能濫用閉包,否則就會造成網頁性能問題,甚至內存泄漏。當我們不需要這些變量的時候,我們可以把這些變量的值賦值為null。
