JavaScript 中的闭包
参考https://github.com/mqyqingfeng/Blog/issues/9 参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
定义
MDN 对闭包的定义为
闭包是指那些能够访问自由变量的函数。
那什么是自由变量呢?
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
由此,我们可以看出闭包共有两部分组成:
闭包 = 函数 + 函数能够访问的自由变量
《JavaScript 权威指南》英文原版对闭包的定义:
This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.
使用闭包的目的
利用闭包实现数据私有化或模拟私有方法。这个方式也称为模块模式(module pattern)
部分参数函数(partial applications)柯里化(currying)
example:
var a = 1
function foo() {
console.log(a)
}
foo()
2
3
4
5
6
7
foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。 由此构成了闭包。
ECMAScript 中,闭包指的是:
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
analysis
var scope = 'global scope'
function checkscope() {
var scope = 'local scope'
function f() {
return scope
}
return f
}
var foo = checkscope()
foo()
2
3
4
5
6
7
8
9
10
11
这个结果显然是 local scope。
在 checkscope 函数执行过程中,我们执行了函数 f。在 f 执行时,checkscope 的函数上下文已经被销毁,此时函数 f 用过 f 的函数的作用域链找到了 scope。这就是闭包的实现。
question
var data = []
for (var i = 0; i < 3; i++) {
data[i] = function() {
console.log(i)
}
}
data[0]()
data[1]()
data[2]()
2
3
4
5
6
7
8
9
10
11
答案都是 3。
当执行到 data[0]()
之前,
globalContext = {
VO: {
data: [...],
i: 3
}
}
2
3
4
5
6
当执行到 data[0]
时,
data[0]Context = {
Scope: [AO, globalContext.VO]
}
2
3
data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。
改成闭包的
var data = []
for (var i = 0; i < 3; i++) {
data[i] = (function(i) {
return function() {
console.log(i)
}
})(i)
}
data[0]()
data[1]()
data[2]()
2
3
4
5
6
7
8
9
10
11
12
13
答案 0,1,2
闭包只能取得包含函数中的任何变量的最后一个值
闭包中的 this 对象
var name = 'window';
var obj = {
name : 'object',
getName:function(){
return function(){
return this.name;
}
}
}
console.log(obj.getName()());
2
3
4
5
6
7
8
9
10
在上面这段代码中,obj.getName()()实际上是在全局作用域中调用了匿名函数,this 指向了 window。 window 才是匿名函数功能执行的环境。
如果使 this 指向外部函数的执行环境
var name = 'window';
var obj = {
name : 'object',
getName:function(){
var self = this
return function(){
return self.name;
}
}
}
console.log(obj.getName()())
2
3
4
5
6
7
8
9
10
11
在闭包中,arguments 与 this 也有相同的问题。下面的情况也要注意:
var name = 'window';
var obj = {
name :'object',
getName:function(){
return this.name;
}
};
obj.getName();//object
(obj.getName = obj.getName)();//window 非严格模式下
2
3
4
5
6
7
8
9
内存泄漏
闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个 HTML 元素,那么就意味着该元素无法被销毁。所以我们有必要在对这个元素操作完之后主动销毁
function handler(){
var element = document.getElementById('someElement');
var id = element.id;
element.onclick=function(){
alert(id);
};
element = null;
}
2
3
4
5
6
7
8
函数内部的定时器
当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。
(function(){
var a = 0;
setInterval(function(){
console.log(a++);
}, 1000);
})();
2
3
4
5
6
闭包的应用
应用闭包的主要场合是:设计私有的方法和变量。 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数、局部变量和函数内定义的其他函数。
闭包的缺陷
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
练习
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
}
}
var a = fun(0); //undefind
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
var b = fun(0).fun(1).fun(2).fun(3);
// undefind 0 1 2
var c = fun(0).fun(1); // undefind 0
c.fun(2); // 1
c.fun(3); // 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20