闭包的概念、特性以及实际应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012863664/article/details/79322240

1. 什么是闭包?

闭包(closure)是函数式编程中的概念,最高实现闭包的语言是Scheme。闭包的严格定义是”由函数(环境)机器封闭的自由变量组成的几何体”,由于这个解释有些晦涩难懂,所以我们先通过一些例子来简单解释一下什么叫做闭包,然后再举一些实际应用中的例子

2. 一个实例

var generateClosure = function() {
  var count = 0;
  var get = function() {
    count++;
    return count;
  };
  return get;
}
var counter = generateClosure();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3

我们都知道,在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数调用的结束而被销毁。因此,正常情况下gernerateClosure函数执行完毕后变量count应该会被释放掉,然而在下面执行的结果中可以看到,不但能够正常输出count,而且每次执行还能够正常加1,这种现象产生的原因正是闭包。

在这里当我们执行 var counter = generateClosure();时,generateClosure返回了一个函数的引用,它可以访问到 generateClosure 被调用时产生的环境,而局部变量count一直处在这个环境里。既然局部变量所在的环境还能被外界 访问,这个局部变量就有了不被销毁的理由。在这里产生了一个闭包结构,局部变量的生命看起来被延续了。这里就是闭包的一个特性:局部变量不会被销毁而是一直保存在内存中

3. 闭包的特性

闭包主要有2个特性:
(1)内部函数可以引用外部函数的局部变量
通过这样可以实现的功能就是,把一些并不需要声明为全局函数的变量定义为局部变量,避免污染全局变量

(2)外部函数中声明的局部变量不会消失,而是会一直保存在内存中。每创建一个新的闭包,就会在内存中生成一个外部函数的成员变量的副本

4. 闭包在实际项目中的应用

4.1 for循环中输出索引值
比如有一个需求,页面上有3个按钮,点击这些按钮之后输出他们的索引值

var aBtn = document.getElementsByClassName('btn');
for (var i=0; i<aBtn.length; i++) {
  aBtn[i].onclick = function() {
    alert(i);
  }
}

如果你是按照上面这种写法,最终的结果肯定都是3,因为当点击的时候alert出来的是全局变量i,此时i经过循环结束后已经变成了3,不管你点击哪一个按钮,弹出的结果都是3,那么怎么做才能实现真正想要的效果呢?看下面代码:

var aBtn = document.getElementsByClassName('btn');
for (var i=0; i<aBtn.length; i++) {
  (function(i) {
    aBtn[i].onclick = function() {
      alert(i);
    }
  })(i)
}

上面这种写法是在闭包的帮助下,把每次循环的 i 值都封闭起来。当在事件函数中顺着作用域链从内到外查找变量 i 时,会先找到被封闭在闭包环境中的 i,如果有 5 个 div,这里的 i 就分别 是 0,1,2,3,4:

4.2 封装变量
比如说有一个需求,我们在应用中需要执行多次计算,每次计算这个过程需要耗费大量的资源,那么我们可以去判断此次计算是否已经在之前已经计算过了,如果是的话就可以直接从缓存里面拿,而不需要执行计算操作。下面这个例子是计算阶乘的。

var mult = (function() {
  var cache = {};
  var calculate = function() {
    var a =0;
    for (var i=0, len = arguments.length; i<length; i++) {
      a = a * arguments[i];
    }
  }
  return function () {
    var args = Array.prototype.join.call(arguments, ',');
    if (args in cache) {
      return cache[args];
    }
    cache[args] = calculate.apply(null, arguments);
    return cache[args];
  }
})();
mult(1, 2, 3, 4, 5);    // 120

通过上面这个例子,我们可以把一些计算结果保存在匿名函数的局部变量cache中,然后每次当我调用函数时回去这个变量中查看之前是否计算过同样的参数,如果是的话则直接从这个对象里面获取计算好的结果并返回,如果没有计算过,那么执行calculate进行计算,并将计算结果保存在cache中,并返回计算结果。

这种做法有下面2种好处:
(1) 避免污染全局环境
如果不使用闭包,那么我需要去全局定义一个cache变量,一个calculate方法,还要定义用来查看是否计算过的那个匿名函数,这样一下子就声明了3个变量或方法,给全局环境造成一定的污染。
(2)防止误操作
通过闭包的方式将cache变量封装为一个私有变量,只能通过mult方法去访问它,外部无法访问。这样就可以避免在全局环境下如果别的位置也有一个cache变量, 别人进行一些操作去改变了它的值,那么在这里就无法正常使用了

4.3 封装一些组件
有一个需求,页面上要添加一个遮罩层,一般的写法就是直接document.createElement(‘div’),然后给div设置一些属性,或者是在页面上写一个不可见的div,适当的时候设置display: block这样。那么下面是另外的一种写法:

function Modal() {
  var modal;
  init();
  return new _constructor();

  function _constructor() {
    this.modal = modal;
    this.show = show;
    this.hide = hide;
  } 

  function init() {
    modal = document.createElement('div');
    modal.className = 'modal hide';
    document.body.appendChild(modal);
  }

  function show() {
    modal.classList.remove('hide');
  }

  function hide() {
    modal.classList.add('hide');
  }
}
var modal = new Modal();
modal.show();
modal.hide();

通过这种写法,我们就可以new Modal之后生成一个实例对象,通过该实例对象的show和hide方法就可以控制遮罩层是显示还是隐藏。这种写法有几个好处:

(1)组件化思想,使用简单,扩展简单
如果除了显示和隐藏还有别的需求的话,就在Modal里面去添加一些function或者属性,但是我的使用方法不需要改变,依旧只要new Modal(),然后通过返回的实例对象去执行一些方法,就能执行操作。

(2)避免全局污染
不用在全局环境定义很多function

5. 缺点

正如上面所提到的特性,闭包中的局部变量不会释放掉,函数执行完毕后还是会保存在内存中,因此会耗费一定的资源。因此不建议在程序中使用大量的闭包,当然少量的使用影响不大。

以上的内容是我的个人理解,欢迎学的比我深入的大神的批评和指点。

猜你喜欢

转载自blog.csdn.net/u012863664/article/details/79322240