浅谈闭包

浅谈闭包

闭包是前端开发者必须要了解的一个知识点,说起闭包,它与变量的作用域跟变量的生命周期紧紧联系,那么我们就一起了解下闭包吧:

变量作用域

首先看个例子:

1
2
3
4
5
6
function fn() {
var a = 110; // a为局部变量
console.log(a); // 110
}
fn();
console.log(a); // a is not defined 外部访问不到内部的变量

上面代码就展示变量的作用域的问题,函数外部是拿不到变量a的值,那么怎么样才能拿到a的值呢?
且看下面的例子:

1
2
3
4
5
6
7
8
9
function fn() {
var a = 110; // a为局部变量
return function() {
return a;
}
console.log(a); // 110
}
var fn2 = fn();
var val = fn2(); // 110

这个时候函数外部val就拿到了a的值,那么到此为止,我们看见了闭包的一个意义:
闭包就是能够读取函数内部变量的函数

变量的生命周期

对于变量的生命周期分几种情况:

  1. 对于全局的变量来说,它的生命周期是永久的,除非我们主动干掉他,销毁他
  2. 对于存在于函数的中的局部变量的话,就没那么幸运了,当函数执行完毕之后,局部变量就销毁了

例如下面的代码:

1
2
3
4
5
function fn() {
var a = 123; // fn执行完毕后,变量a就将被销毁了
console.log(a);
}
fn();

我们再来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
function add () {
var a = 0
return function () {
a++
console.log(a)
}
}

var fn1 = add()
fn1() // a = 1
fn1() // a = 2

在这段代码里面 局部变量a就保存下来了,并没有被销毁,正是由于闭包的缘故,返回的这个function里面还有用到a 所以a就被保存下来了

闭包的作用

  1. 可以封装私有变量,可以吧一些不需要暴露出去的私有变量封装在函数内部,这样就会减少全局变量的污染,而且也不会造成变量名冲突等等情况发生

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var sum = (function() {
    var cache = {}; // 将cache放入函数内部,避免被其他地方修改
    return function() {
    var args = Array.prototype.join.call(arguments, ',');
    if (args in cache) {
    return cache[args];
    }
    var a = 0;
    for (var i = 0; i < arguments.length; i++) {
    a += arguments[i];
    }
    return cache[args] = a;
    }
    })();
  2. 模块化

    1
    2
    3
    4
    5
    6
    7
    8
    (function(win, undefined) {
    var a = 1;
    var obj = {};
    obj.fn = function() {};

    // 最后把想要暴露出去的内容可以挂载到window上
    win.obj = obj;
    })(window);
  3. 将变量的使用延长

1
2
3
4
5
6
7
8
9
10
var monitor = (function() {
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
})();

monitor('http://dd.com/srp.gif');

闭包引起的坑

看一段经典的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick = function() {
console.log(i); // ?
};
}
</script>

这里无论如何点返回的都是4,因为onclick是一个异步的操作,当点击的时候,循环都已经结束了,这时候i的值已经是4了,如果想要返回0,1,2,3,4看下面的例子

1
2
3
4
5
6
7
8
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
(function(n) { // n为对应的索引值
aLi[i].onclick = function() {
console.log(n); // 0, 1, 2, 3
};
})(i); // 这里i每循环一次都存一下,然后把0,1,2,3传给上面的形参n
}

用立即执行函数,把i的值存下来,当使用的时候就可以拿到循环当时的值

内存泄露问题

过度的使用闭包,就造成很多变量无法销毁,就会导致内存的泄露,所以当有些变量被使用完之后,如果没有存在的必要了,应当及时的释放掉,减少内存的开销,如何释放呢?直接把引用中的变量设为null 即可