前言

在很久很久以前,在古乾断,一个叫做嘉瓦斯克瑞普特的地方,出了一条恶龙,它口吐紫光,毁天灭地,伸出的触手能让最强壮士兵消失。在这个地方自古以来流传着这样一个预言,当作用域之剑显世之时,就是巨龙毁灭之日。

切入正题,在学习了作用域之后我们可以进一步了解闭包这个概念,在刚刚接触JavaScript时,就一直有听说,闭包很重要但是很难理解。写这篇文章的初衷便是让大家深入理解闭包的概念,因此接下来我会慢慢讲述何为闭包,闭包又有什么作用,希望大家看完之后有所收获,话不多说,进入今天的屠龙之旅。

重学JavaScript(四)深入理解闭包-LMLPHP

闭包的概念

👆上面的概念取自于维基百科,上述概念中其实蕴含了两种概念,我个人倾向于第一种,即引用了自由变量的函数为闭包

闭包的定义在js学习中一直是非常模糊的,可以说每个人对于闭包的理解是不同的,我认为闭包的是作用域的产物,姑且认为闭包是指一个可以访问另一个函数作用域中变量的函数。

function foo (){
   function bar (){
      console.log(aaa)
   }
   var aaa =123;
   return bar
}
var demo = foo()
demo()  // 123
复制代码

在上述的例子,输出的是123,但是根据我们对作用域的理解,aaa属于函数内部作用域中的变量,而demo的运行环境处于全局作用域,按理说外部环境并不能访问内部函数的变量。很多小伙伴都应该明白,这是运用了闭包,那么接下来我们需要了解为什么会产生闭包而闭包又是何时产生的。

进一步了解闭包

根据前面作用域的学习,我们可以了解上述例子的执行过程,这里我简单将其列出:

1.foo函数定义,形成foo函数的作用域链,作用域链的第一位为全局执行上下文

重学JavaScript(四)深入理解闭包-LMLPHP

2.foo函数执行,作用域的第一位,改为foo函数的局部执行上下文,第二位则是之前的全局执行上下文。在foo函数执行的同时,我们迎来了bar函数的定义。形成了bar函数的作用域链,由于bar在函数foo内被定义,它可以访问foo的作用域,使以bar的作用域链上天生就有foo的作用域链,所以下面我将bar的作用域链指向foo的作用域链。同时bar函数被return赋值给了demo这个全局变量。

重学JavaScript(四)深入理解闭包-LMLPHP

3.bar函数执行,bar函数作用域链第一位改为自身局部执行上下文,执行 console.log(aaa) 按照作用域链查找,输出123

重学JavaScript(四)深入理解闭包-LMLPHP

foo函数执行完毕之后,js自带的垃圾回收机制,会将其标记,并在下次回收时,释放foo函数所占用的内存空间,但是好巧不巧,你需要释放的作用域链,已经作为bar的作用域链的一部分被带到外部了,那指定不能让你销毁呀。

这时闭包就产生了,在我的理解中,bar被foo返回时就产生了闭包,此时内部函数和外部函数同处于一个作用域下,bar的作用域链上绑定了foo的作用域链,导致foo函数作用域链不能释放。不知道大家有没有理解呢?

重学JavaScript(四)深入理解闭包-LMLPHP

闭包会产生内存泄漏吗?

不再用到的内存,没有及时释放,就叫做内存泄漏。更进一步的说当一个变量我们已经无法通过js来引用了,但是垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它。导致了分配的这块内存永远也无法被释放出来。

那么闭包会产生内存泄漏吗?这是经常被讨论的问题,我个人认为不会,因为我理解中的闭包时在内部函数被返回到外部时,闭包产生了。而内存泄漏出现在bar函数被执行时,因此我认为aaa变量不被释放,并不是由闭包导致的,你可以通过将demo变量使用完后,指向新的地址,来手动释放这一部分内存。由于闭包并没有准确的定义,上述仅是我个人理解,当然还有很多人认为闭包是foo函数并且导致了内存泄漏,欢迎大家在下面讨论。

闭包的运用

私有化变量

原生 JavaScript 并没有支持私有变量,但我们可以使用闭包来模拟私有变量或者方法。私有变量不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共部分。

(function (ROOT) {
	 var jQuery = function(selector) {
        return new jQuery.fn.init(selector)
   }

   jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        init: function(selector) {
            var elem, selector;
             elem = document.querySelector(selector);
            this[0] = elem;
            return this;
        }
    }
   ....


  ROOT.jQuery = ROOT.$ = jQuery;
})(window);
复制代码

jQuery就是私有化变量的典型应用

循环中的闭包

在 ES6 引入块级作用域之前,在循环中有一个常见的创建问题。我们可以运用闭包解决下面这个异步问题,这里就贴一下代码。

  for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
      console.log(i);
  }, 1000 );
  }

 for (var i = 1; i <= 5; i++) {
    (function(i){
        setTimeout( function () {
              console.log(i);
          },  1000 );
    })(i);
 }

 for (let i = 1; i <= 5; i++) {
      setTimeout( function timer() {
               console.log(i);
      }, 1000 );
  }
复制代码

写在最后

经过这篇文章的梳理,自己对闭包对理解又有些加深,希望这篇文章能给读者带来新的思考,解决了闭包这头恶龙后,我们将继续Javascript的探索之旅,下一站我们将了解什么是原型,去体会JavaScript的魅力。


作者:TimCope
链接:https://juejin.im/post/5de4cdd16fb9a071b3011ccc
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

12-03 15:49