JavaScript深入浅出-闭包及闭包问题的解决办法

闭包是什么?

在JS中可谓处处是闭包,也许你不知道这个概念,但是你一定自己手写过闭包,而且还碰到了闭包的问题.

闭包的最大作用还是解决了全局作用域中的变量被污染的问题.

但是闭包虽有好处,但是也不能滥用,因为闭包的调用机制使得垃圾回收机制(GC)在闭包使用完毕后,无法对其进行回收,使用过多的话会影响到性能.特别是在IE浏览器中,因为IE浏览器在IE8及之前的GC机制使用的是引用计数方式,所以使用闭包会产生内存泄露.万幸在IE9中更正了这个问题,现在绝大部分浏览器实现的是标记清除式的GC机制.

如果必须使用闭包的时候,笔者建议可在闭包使用完之后,可以将其引用变量设置为null,以便清除其占用的内存空间.

一个很简单的闭包例子

1
2
3
4
5
6
7
8
9
function add() {
var a = 100;
return function() {
return a++;
}
}
var a = add();
console.log(a()); //100
console.log(a()); //101

看,这就是闭包了,闭包的调用也很方便,直接执行函数中的函数,就可以了.

闭包产生的问题和解决方法

其实闭包最大的危害就是在函数进行循环调用的时候以及使用回调函数的时候了,之前没懂闭包,做这类问题的时候是相当的头痛啊,这里我就简单介绍一下如何解决这类问题.下面以循环调用为例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function returnArr() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function() {
return i
}
}
return arr;
}
var a = returnArr();

for (var i = 0; i < 5; i++) {
console.log(a[i]()) //5 5 5 5 5
}

可能这个时候很多人会觉得很奇怪,这是什么情况啊,我明明是想要输出0 1 2 3 4的啊!这个时候,你其实已经写了一个闭包了.

闭包问题的产生原因

在循环体中,当我们像平常一样调用函数中的函数的时候,其实这个时候循环体早就执行完毕了,所以这个时候输出的值是一个固定的值,在本题中输出的是5,因为循环一直到4时跳转,在循环到四之后最后还有一个4++的操作,最终输出的是5

解决闭包问题的方案

1.将函数体转换为自执行函数,这个时候就可以在循环的同时将函数一起执行了,所以i的值可以正确地传入,但是因为自执行函数是一个封闭的作用域,所以在这个时候应该将i的值作为参数传递到这个封闭作用域中,最后可以得到想要的输出结果.

1
2
3
4
5
6
7
8
9
10
11
function returnArr() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function(a) {
return a
}(i)
}
return arr;
}
var a = returnArr();
console.log(a); //0 1 2 3 4

2.将函数中再嵌套一个函数,然后再执行以下函数中的函数,这样也可以解决闭包问题,但是为什么我在里面又嵌套了一个匿名函数,却没有再次产生闭包的问题呢?这是因为当我们传参给自执行函数时,其参数时直接驻留在自执行函数的内存中的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function returnArr() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function(a) {
return function() {
return a;
}
}(i)
}
return arr;
}
var a = returnArr();
for (var i = 0; i < 5; i++) {
console.log(a[i]())
}

闭包的时候的this指向问题

产生闭包的时候,闭包里的this其实是指向window的.

1
2
3
4
5
6
7
8
var box = {
getThis: function() {
return function() {
return this;
}
}
}
console.log(box.getThis()()) //window

那么当我在使用闭包的时候,想让这个this再指回我们的调用对象怎么办呢?这个时候我们的call方法就派上用场了.

很多时候,call和apply的用法是类似的,只是传参不同而已,大部分情况下使用call也意味着可以使用apply.apply就不演示了.其实两者都是一样的.

1
2
3
4
5
6
7
8
var box = {
getThis: function() {
return function() {
return this;
}
}
}
console.log(box.getThis().call(box)) //box

当我们不想用call或者apply方法怎么办呢?ES5中新增了一种方法就是bind(),可以将函数的this硬绑定到一个对象上.

1
2
3
4
5
6
7
8
9
var box = {
getThis: function() {
return function() {
return this;
}
}
}
var a = box.getThis();
console.log(a.bind(box)()) //box

觉得上面的方法复杂,不想用?别急还有一种暴力方法解决这个问题,就是直接在函数体中动刀.

1
2
3
4
5
6
7
8
9
var box = {
getThis: function() {
var that = this;
return function() {
return that;
}
}
}
console.log(box.getThis()()) //box

利用闭包来模仿块级作用域

包含自我执行的匿名函数,就可以创建私有作用域

1
2
3
4
5
6
7
8
(function() {
(function() {
for (var i = 0; i < 5; i++) {

}
})()
console.log(i) //ReferenceError: i is not defined
})()

那么根据以上,就可以引申出各种设计模式了.比如说单例模式啊,工厂模式啊.构造函数模式啊巴拉巴拉.这个在我的JavaScript高级程序设计复习笔记(三)这一篇博客里面有详细讲解.

文章目录
  1. 1. 闭包是什么?
  2. 2. 一个很简单的闭包例子
  3. 3. 闭包产生的问题和解决方法
    1. 3.1. 闭包问题的产生原因
    2. 3.2. 解决闭包问题的方案
  4. 4. 闭包的时候的this指向问题
  5. 5. 利用闭包来模仿块级作用域
|