首先引用 MDN 文档的一句话作为开头
闭包是函数和声明该函数的词法环境的组合。
闭包的概念
当一个函数被 return
的时候,这个函数内部的词法作用域中的变量是可以被外界访问到的,外层函数执行完毕时被销毁,但由于内部函数作为值返回出去,这些值得以保存下来,存储在内存中,也就是私有性。
一个基本的例子:
// 来自 MDN
function makeFunc() {
var name = "DOG";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。执行 makeFunc
时创建的 displayName
函数实例的引用,而 displayName
实例仍可访问其词法作用域中的变量,即可以访问到 name
。由此,当 myFunc
被调用时,name
仍可被访问。
闭包的应用
私有属性
在 JavaScript 中,是没有原生支持私有属性的(据说现在已经有了提议),但是我们可以使用闭包来创建一个私有属性。
// 来自 MDN
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter = makeCounter();
console.log(counter.privateCounter); // undefined
console.log(counter.value()); // 0
存储变量
function func() {
var x = 100;
return {
function() {
return x;
}
}
}
var m = func(); //运行函数,变量 x 被 m 引用
此时 m
引用了变量 x
,所以函数执行后 x
不会被释放,可以把比较重要或者计算耗费很大的值存在 x
中,只需要第一次计算赋值后,就可以通过 m
函数引用 x
的值,不必重复计算,同时也不容易被修改。
导致的问题
看一个例子:
var a = [];
for(var i = 0; i < 10; i++) {
a[i] = () => {
return i;
};
}
a[6](); //10
在这个简单的函数中,变量 i
是在 for
循环中定义的,如果是在 C++ 或者 Java 中,这样定义的变量,一旦循环结束,变量也就随之销毁,i
的作用范围只在循环这个小块,就称为块级作用域。在 JavaScript中,没有这样的块级作用域。所以上一段代码其实相当于:
var a = [];
var i = 0;
for(; i < 10; i++) {
a[i] = () => {
return i;
};
}
a[6](); //10
这样就很好理解了。由于匿名函数里面没有 i
这个变量,在函数执行的时候,这个 i
他要从父级函数中寻找,而父级函数中的 i
在for
循环中,当找到这个 i
的时候,是 for
循环已经循环完毕,所以所得与预想不一致。
改进:
var a = [];
for(var i = 0; i < 10; i++) {
a[i] = (() => {
return i;
})();
}
a[6]; // 6
总结
看到了一段很有用的话:
当一个子函数被创建时,是父函数的执行导致的,所以当子函数创建时,父函数已经处于执行阶段,所以父函数的执行上下文已经创建了。同时,因为子函数也在父函数的局部变量作用域内,所以,子函数在创建的时候,除了要引用全局上下文,也需要引用父函数的执行上下文。当一个子函数执行时,因为它同样是函数,所以它同样需要创建自己的执行上下文,当它返回的时候,同样也只解除属性中对自身执行上下文的引用,对父函数的执行上下文的引用并没有解除,这意味着,父函数的执行上下文与子函数本身共存亡了。只要子函数还存在引用,垃圾收集器就不会销毁它们所在的执行上下文。另外,因为父函数的局部变量并不在全局上下文中,所以它只能在子函数的变量解析中被访问,自然而然就相当于它们是子函数私有的了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。