谈谈JS闭包

107 阅读3分钟

定义:闭包是指有权访问另一个函数作用域中变量的函数。

1、闭包与变量 作用域链的这种配置机制引出了一个值得注意的副作用即闭包只能取得包含函数中任何变量的最后一个值。 我们首先来看一个很常见的场景

function createFunction() {
    var result = new Array();
    for (var i = 0; i < 10; i++ ) {
        result[i] = function() {
            return i;
        }
    }
    return result;
}

这个函数会反回一个函数数组。表面上看,似乎每个函数都应该返回自己的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上每个函数都返回10因为每个函数作用域链都保存着createFunction()函数的活动对象,所以他们引用的都是同一个变量i。这时候就需要闭包上场了,如果我们想要达到预期效果需要这么写

function createFunction() {
    var result = new Array();
    for (var i = 0; i < 10; i++ ) {
        result[i] = function(num) {
            return function(){
               return num;
           };
        }(i)
    }
    return result;
}

这个版本写法中我们没有直接把闭包赋值给数组,而是定一个一个匿名函数,并立即函数立即执行结果赋给数组。这里匿名函数有一个参数num,也就是最终函数要返回的值。在调用匿名函数时我们传入了变量i。由于函数参数是按值传递的,索引就会将变量i的当前值复制给num。而这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中每个函数都有自己num变量的一个副本,因此就可以返回不同的值了。 2、闭包中的this 闭包中使用this对象可能会导致一些问题。我们知道this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。我们来看下面的例子。

var name = "this window"
var object = {
      name : "my object",
      getNameFunc: function() {
          return function() {
              return this.name;
          }
      }
}
alert(oject. getNameFunc()()); // "this window"

不过如果把外部作用域中的this包存在一个闭包能访问到的变量里,就可以让闭包访问该对象了。

var name = "this window"
var object = {
      name : "my object",
      getNameFunc: function() {
          var that = this;
          return function() {
              return that.name;
          }
      }
}
alert(oject. getNameFunc()()); // "this window"

3、内存泄漏 具体来说,如果闭包作用域链中保存着html元素,那么就意味着该元素将无法销毁。来看个例子

function assignHandler() {
   var element = document.getElementById("omeElement");
   element.onclick = function() {
      alert(element.id);
   } 
}

由于匿名函数保存了一个对assignHandler() 活动对象的引用,因此就会导致element的引用数最少也是1,因此它的内存永远不会被收回。我们改写成以下代码就能解决

function assignHandler() {
   var element = document.getElementById("omeElement");
   var id = element.id;
   element.onclick = function() {
      alert(id);
   } 
   element = null;
}

通过element.id的一个副本保存在变量中,还是不能完全解决内存泄漏的。必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象仍然会保存一个引用。因此有必要把element设置为null。 4、模仿块级作用域 es6之前只有全局作用域和函数作用域是没有块级作用域概念的,但通过闭包可以在函数内模仿写法如下

(function(){
   // 这里是块级作用域
})()

下面是如何在函数内使用私有作用域

funtion outputNumbers(count){
    (function(){
        // 这里是块级作用域
        for(var i = 0; i < count; i++){
             alert(i);
        }
    })();
    alert(i);  // 导致一个错误
}