閉包
MDN
面試官問我什么是閉包該如何回答
廖雪峰博客-閉包
阮一峰博客-閉包
個人理解
內(nèi)部函數(shù)可以訪問外部函數(shù)的作用域, 如果在內(nèi)部函數(shù)持有了外部函數(shù)的變量等, 并將內(nèi)部函數(shù)return出去,導致這個變量不能被銷毀
function outer() {
var a= 1; // 定義一個內(nèi)部變量
return function () {
return a; // 返回a的變量值
}
}
var b = outer()
console.log(b()) ===> 1
產(chǎn)生一個閉包
閉包的作用域鏈包含著它自己的作用域柜某,以及包含它的函數(shù)的作用域和全局作用域耕魄。
function func() {
let a = 1, b = 2
function closure() { // 閉包
return a + b // 返回a+b的值
}
return closure // 返回閉包函數(shù)
}
閉包的注意事項
通常囤萤,函數(shù)的作用域及其所有變量都會在函數(shù)執(zhí)行結束后被銷毀。但是酌伊,在創(chuàng)建了一個閉包以后,這個函數(shù)的作用域就會一直保存到閉包不存在為止卧土。
function makeAddr(x) {
return function(y) {
return x+y
}
}
var add5 = makeAddr(5)
var add10 = makeAddr(10)
console.log(add5(2)) ===> 7
console.log(add10(10)) ===> 12
<!-- 釋放對閉包的引用 -->
add5 = null
add10 = null
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在這段代碼中盹舞,result實際上就是閉包f2函數(shù)。它一共運行了兩次倡蝙,第一次的值是999九串,第二次的值是1000。這證明了寺鸥,函數(shù)f1中的局部變量n一直保存在內(nèi)存中猪钮,并沒有在f1調(diào)用后被自動清除。
為什么會這樣呢析既?原因就在于f1是f2的父函數(shù)躬贡,而f2被賦給了一個全局變量谆奥,這導致f2始終在內(nèi)存中眼坏,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中酸些,不會在調(diào)用結束后宰译,被垃圾回收機制(garbage collection)回收。
這段代碼中另一個值得注意的地方魄懂,就是"nAdd=function(){n+=1}"這一行沿侈,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量市栗,而不是局部變量缀拭。其次咳短,nAdd的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包蛛淋,所以nAdd相當于是一個setter咙好,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進行操作。
注意點
由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中褐荷,內(nèi)存消耗很大勾效,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題叛甫,在IE中可能導致內(nèi)存泄露层宫。解決方法是,在退出函數(shù)之前其监,將不使用的局部變量全部刪除萌腿。
閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值抖苦。所以哮奇,如果你把父函數(shù)當作對象(object)使用,把閉包當作它的公用方法(Public Method)睛约,把內(nèi)部變量當作它的私有屬性(private value)鼎俘,這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值辩涝。
返回的函數(shù)并沒有立即執(zhí)行贸伐,而是直到調(diào)用了才執(zhí)行
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
<!--
期望值為 1 4 9
實際輸出入下
-->
f1() ===> 16
f2() ===> 16
f3() ===> 16
將var改成let即可正確輸出
全部都是16!原因就在于返回的函數(shù)引用了變量i怔揩,但它并非立刻執(zhí)行捉邢。等到3個函數(shù)都返回時,它們所引用的變量i已經(jīng)變成了4商膊,因此最終結果為16伏伐。返回閉包時牢記的一點就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量晕拆。如果一定要引用循環(huán)變量怎么辦藐翎?方法是再創(chuàng)建一個函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當前的值实幕,無論該循環(huán)變量后續(xù)如何更改吝镣,已綁定到函數(shù)參數(shù)的值不變:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
注意這里用了一個“創(chuàng)建一個匿名函數(shù)并立刻執(zhí)行”的語法:
(function(n){
return n*n;
})(3); ===> 9
閉包中的this
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
===> The Window
在上面這段代碼中,obj.getName()()實際上是在全局作用域中調(diào)用了匿名函數(shù)昆庇,this指向了window末贾。這里要理解函數(shù)名與函數(shù)功能是分割開的,不要認為函數(shù)在哪里整吆,其內(nèi)部的this就指向哪里拱撵。window才是匿名函數(shù)功能執(zhí)行的環(huán)境辉川。
如果想使this指向外部函數(shù)的執(zhí)行環(huán)境,可以這樣改寫:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
===> My Object
在閉包中拴测,arguments與this也有相同的問題员串。下面的情況也要注意:
var name = "The Window";
var object = {
name : "My Object",
getName : function(){
retuen this.name;
}
};
object.getName() ===> My Object
(Object.getName = Object.getName)() ===> The Window
obj.getName();這時getName()是在對象obj的環(huán)境中執(zhí)行的,所以this指向obj昼扛。
(obj.getName = obj.getName)賦值語句返回的是等號右邊的值寸齐,在全局作用域中返回,所以(obj.getName = obj.getName)();的this指向全局抄谐。要把函數(shù)名和函數(shù)功能分割開來渺鹦。
閉包的主要應用場景
- 私有變量 實現(xiàn) private的功能
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的對象中,實現(xiàn)了一個閉包蛹含,該閉包攜帶了局部變量x毅厚,并且,從外部代碼根本無法訪問到變量x浦箱。換句話說吸耿,閉包就是攜帶狀態(tài)的函數(shù),并且它的狀態(tài)可以完全對外隱藏起來
- 把多參數(shù)的函數(shù)變成單參數(shù)的函數(shù)
'use strict';
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 創(chuàng)建兩個新函數(shù):
var pow2 = make_pow(2);
var pow3 = make_pow(3);
console.log(pow2(5)); // 25
console.log(pow3(7)); // 343