其他分享
首页 > 其他分享> > 真正理解一下闭包!

真正理解一下闭包!

作者:互联网

闭包,可谓是面试常谈,什么是闭包 ?闭包是用来做什么的 ?

之前在准备一些面试时,我都会搜查一番,< 闭包 > 在各种技术书籍和博客中,都有着各种解释和分析,

比如:“ 闭包就是指有权访问另一个函数作用域中的变量的函数 ” ;“ 声明在一个函数中的函数,叫做闭包函数 ”,“ 闭包函数会造成内存泄漏” 等等等, 随后又分析了几个代码案例,然后,“ 嗯,原来这就是闭包啊! ”。

但其实真实情况,还是似懂非懂,但是在 实际应用中,闭包很常见,今天,再来彻底总结一下闭包,到底它是个神马玩意er

参考了一些大佬的文章和一些问答讨论,附上:

js中的闭包是什么?:https://zhuanlan.zhihu.com/p/22486908

难于理解的闭包 : https://segmentfault.com/q/1010000040284976?utm_source=sf-homepage

学习javascript闭包 : https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

1. 如何定义闭包

先来看一个简单的例子:

简化一下,假设以下三行代码,放在一个立即执行函数

    var name = "Mozilla";

    function displayName() {
      alert(name);
    }

如上代码,在displayName 函数内部,可以访问到定义在这个立即执行的函数中的 name属性,这就形成了一个简单的闭包

( displayName函数+函数内部可以访问到的name属性 )

很多人可能会有疑惑,这个就是闭包吗,就这 ??

“ 我怎么听说闭包是需要函数套函数,然后 return 函数的呀!” 就像下面

    function makeFunc() {
      var name = "Mozilla";

      function displayName() {
        alert(name);
      }
      return displayName;
    }

    var myFunc = makeFunc();
    myFunc();

这确实也有闭包,displayName函数和name属性就组成了一个闭包

为什么需要函数套函数呢?

是因为需要局部变量,所以把name放在 了一个函数里,如果不把name放在一个函数内,name就变成一个全局变量了

有些人看来,闭包,闭包,**包,**一定要有什么被包起来才行,然鹅,这只是一个翻译问题,闭包的原文是 closure,与包无关。

可以理解为,函数套函数只是为了造出一个局部变量,跟闭包无关

为什么要return呢

因为如果不 return,你就无法使用这个闭包。把 return displayName 改成 window.displayName = displayName 也是一样的,只要让外面可以访问到这个 displayName 函数就行了。

所以 return displayName 只是为了 displayName 能被使用,也跟闭包无关。

来看一下MDN,对于闭包的解释

< 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。 >

简化一下, 函数和周围状态的引用捆绑,这样的组合就是闭包,由此看来,闭包并不是 “有权访问另一个函数作用域中的变量的函数 ”,闭包甚至都不是函数啊,而是一个组合,正如你不能说情侣是某个男人或某个女人一样, 情侣是两个有情人的组合。

“函数和周围状态”, 函数容易理解,需要解释的就只有“周围状态”了。 MDN 对此的解释是“词法环境”,但是没有给出词法环境的定义,按照我的理解,词法环境由当前位置可访问的全部变量构成,至于哪些变量是“可访问的”,这就是作用域的知识了, 这样看来,考察闭包,无非就是在考察作用域,换句话说,闭包是 JS 函数作用域的副产品

所以这里可以粗略的认为

闭包 ≈ 函数 + 此函数内部能访问的全部变量

2. 闭包的作用

闭包,常用来间接访问一个变量(或者说将一个变量隐藏),为什么要间接访问(隐藏)呢,我直接访问不行吗?举个例子吧

假设一个游戏,在其中写 「还剩几条命的代码」,如果不用闭包,可以直接写一个全局变量(暴露出来,不隐藏)

    window.lives = 30 // 还有三十条命

这样看起来很不妥,万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人 直接访问 这个变量。怎么办呢?

用局部变量,

但是局部变量别人又访问不到了,怎么办?

暴露一个访问器(函数),让别人通过这个访问器(函数),间接访问

    ! function () {

      var lives = 50

      window.奖励一条命 = function () {
        lives += 1
      }

      window.死一条命 = function () {
        lives -= 1
      }

    }()

那么在其他的 JS 文件,就可以使用 window.奖励一条命( ) 来涨命,使用 window.死一条命( ) 来让角色掉一条命。

这样,通过两个函数,可以间接访问到lives这个变量,同时,这样生成了闭包( 函数 + 此函数内部能访问的全部变量 )

3.闭包有何特性

鉴于 JS 代码中至少会有全局作用域存在,所以任何函数都有“周围状态”,也就是说你所见的每一个函数,实际上都属于某个闭包的一部分,而闭包的特性就是 JS 中作用域的特性:函数可以访问其外部环境中的变量。
这简直就是一句废话,到此为止,闭包的特别之处还没有体现出来——因为闭包根本没有特别之处。

学习“闭包”,首先应该搞清楚“作用域”

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

    var n = 999;

    function f1() {
      alert(n);
    }
    f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量

    function f1() {
      var n = 999;
    }
    alert(n); // n is not defined

But,我就要在函数外部得到函数内的局部变量,怎么办呢?

好吧,那只能变通一下,

在函数内部,再定义一个函数

    function f1() {
      var n = 999;

      function f2() {
        alert(n); // 999
      }
    }

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

    function f1() {
      var n = 99

      function f2() {
        alert(n);
      }
      return f2;
    }
    var result = f1();
    result(); // 999

如上,就是我们所熟悉的闭包了,这样看来确实呀,闭包的特性就是 JS 中作用域的特性

内存泄露的谣言!!

另外,关于闭包,很多人会提到内存泄露,那问你一下,什么是内存泄露呢,可能你也说不上个一二三四,人云亦云罢了

那么到底什么是内存泄露,解释一下:

对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

这里简单来说就是 你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来,这就造成了内存泄露。

但是对于闭包来说,闭包里面的变量明明就是我们需要的变量(lives),用到的变量,凭什么说是内存泄露啊,所以,谣言,谣言没错了

那这个谣言是如何来的呢?

因为 IE。IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。

但是这是 IE 的问题,不是闭包的问题。具体可参考

上边这些是看了一些博客资料,了解闭包后,个人的一个总结记录,如有问题,欢迎大家留言评论,多多指教!

标签:闭包,function,displayName,函数,作用域,访问,理解,真正
来源: https://blog.csdn.net/m0_49159526/article/details/119210998