Littlest things

JavaScript中的闭包小结

闭包

和其他大多数现代的语言一样,JavaScript也采用词法作用域(lexical scoping),也就是说,函数的执行依赖于变量的作用域,这个作用域是在函数定义的时候决定的,而不是函数调用时决定的。
为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前作用域链。 –《JavaScript权威指南》

示例

Demo1

1
2
3
4
5
6
7
8
9
var scope = "global scope"
function checkscope() {
var scope = 'local scope'
function f() {
return scope
}
return f
}
checkscope()() // local scope

在JavaScript编程,经常有意无意的都会使用闭包,只是通常不知道。
理解闭包一定要记住JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前作用域链,这句话

Demo2

1
2
3
4
5
6
7
8
9
var x = 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20
f()
}
show(fn) // 10

Demo3

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = (function() {
var value = 0
return {
increment:function() {
value +=1
},
getValue:function() {
return value
}
}
})()
myObject.increment()
myObject.getValue()

在这里闭包体现出了封装性。

Demo4

编程语言中,作用域控制着变量与参数的可见性及生命周期。
大多数类C语言语法的语言都拥有块级作用域。在一个代码块中定义的所有变量在代码块外部是不可见的。定义在代码块中的变量在代码块执行结束后会被释放。这是一件好事。
糟糕的是,尽管JavaScript的代码块貌似支持块级作用域,但实际上并不支持。
JavaScript的函数确实是有函数作用域的。那意味着定义函数中的参数和变量在函数的外部是不可见的,而在一个函数的内部任何位置定义的变量,在函数内部都可见。
–《JavaScript语言精粹》

1
2
3
4
5
6
// 糟糕的写法
for (var i =0 ;i<3;i++){
setTimeout(function() {
alert(i)
},i*1000)
}

通常都会认为结果是 0 1 2 ,但其实并不是而是 3 3 3 。这就是JavaScript没有块级作用域的原因。
上面的例子应该改成

1
2
3
4
5
6
7
8
9
10
11
12
for (var i =0 ;i<3;i++){
(function(index) {
setTimeout(function() {
alert(index)
},index*1000)
})(i)
}
for (let i =0 ;i<3;i++){
setTimeout(function() {
alert(i)
},i*1000)
}

利用函数有作用域的特点,通过立即执行函数创建块级作用域。如果是支持let,将var改成let也是可行的。

闭包引起内存泄露

1
2
3
4
5
6
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}

下面这段代码会造成内存泄露

1
2
3
el.onclick= function () {
alert(el.id);
};

为什么?执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被回收。
解决方法:

1
2
3
4
5
6
7
8
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}

闭包优缺点

优点:

  • 可以让一个变量常驻内存
  • 避免全局变量的污染
  • 私有化变量

缺点:

  • 因为闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存
  • 引起内存泄露