定義
**閉包(closure)**是一個函數以及其捆綁的周邊環境狀態(lexical environment,詞法環境)的引用的組合。換而言之,閉包讓開發者可以從內部函數訪問外部函數的作用域。在 JavaScript 中,閉包會隨著函數的創建而被同時創建。
閉包產生的原因?
閉包的產生原因是因為在函數內部定義的函數可以訪問外部函數的變量和參數,即使外部函數已經執行完畢,內部函數仍然可以訪問那些變量和參數。
1、返回函數
最常用的一種形式是函數作為返回值被返回:(返回一個函數,外界保持對里的引用)
var outer = function(){var b = 'local';var fn = function(){return b;}return fn;
}
console.log(outer()());
2、函數賦值
一種變形的形式是將內部函數賦值給一個外部變量:(將 outer 函數作用域里的函數 fn 賦值給全局作用域的inner,outer 執行之后,inner保持了對 outer 作用域里的引用)
var inner;
var outer = function(){var name = 'Smile';var fn = function(){return name;};inner = fn ;
};
outer();
console.log(inner());
3、函數參數
閉包可以通過函數參數傳遞函數的形式來實現:(將F里的N作為參數給外界的Inner,F執行之后,外界Inner保持了對F里的引用)
var inner = function(fn){console.log(fn());
}
var outer = function(){var name = 'Smile';var fun = function(){return name;}inner(fun);
}
outer();
4、IIFE(立即執行函數)
由前面的示例代碼可知,函數F()都是在聲明后立即被調用,因此可以使用IIFE來替代。但是,要注意的是,這里的Inner()只能使用函數聲明語句的形式,而不能使用函數表達式。
function inner(fn){console.log(fn());
}(function(){var name= 'Smile';var fun = function(){return name;}inner(fun);
})();
5、循環賦值
在閉包問題上,最常見的一個錯誤就是循環賦值的錯誤。
function foo(){var arr = [];for(var i = 0; i < 3; i++){arr[i] = function(){return i;}}return arr;
}
var bar = foo();
console.log(bar[0]());//2
原因:真正執行函數是bar[0]()
,單獨拿出每個數組中第一個函數執行,外層循環已經執行完;其次方法執行產生的私有作用域,用到變量i,私有作用域里不存在 i,會根據作用域查找機制向上查詢,找到的是已經是循環完畢賦值為 3 的全局變量 i
正確的寫法如下
function foo(){var arr = [];for(var i = 0; i < 2; i++){arr[i] = (function fn(j){return function test(){return j;}})(i);}return arr;
}
var bar = foo();
console.log(bar[0]());//0
5、getter & setter
我們通過提供getter()和setter()函數來將要操作的變量保存在函數內部,防止其暴露在外部
var getValue,setValue;
(function(){var num = 0;getValue = function(){return num;}setValue = function(value){if(typeof value === 'number'){num = value;}}
})();
console.log(getValue());//0
setValue(1);
console.log(getValue());//1
6、迭代器
我們經常使用閉包來實現一個累加器
var add = (function(){var count = 0;return function(){return ++count; }
})();
console.log(add())//1
console.log(add())//2
類似地,使用閉包可以很方便的實現一個迭代器
function setup(x){var i = 0;return function(){return x[i++];}
}
var next = setup(['a','b','c']);
console.log(next());//'a'
console.log(next());//'b'
console.log(next());//'c'
總結:
無論通過何種形式,只要將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始作用域的引用,無論在何處執行這個函數都會使用閉包。
參考鏈接
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
- https://www.cnblogs.com/goloving/p/7775065.html
- https://juejin.cn/post/6989817280720797710