第一季 JavaScript 实战沉淀:闭包

阅读时长 6 分钟读完

什么是闭包?

闭包是指一个函数能够访问并操作其所在词法作用域中的变量,即使该函数是在其词法作用域之外被调用的。简单来说,闭包是指函数和函数内部能够访问的变量的总和。

我们可以通过一个例子来理解闭包的概念:

-- -------------------- ---- -------
-------- --------------- -
  --- -------- - -- -- ----- -----------
  -------- --------------- -
    ----------------------
  -
  ------ --------------
-

--- ------- - ----------------
----------

这个代码片段中,innerFunction 定义了一个闭包,因为它可以访问 outerFunction 中的变量 outerVarouterFunction 返回了 innerFunction,也就是说变量 closure 实际上是 innerFunction。最后,我们调用了 closure(),它成功地访问了 outerVar 变量并输出了其中的内容。

为什么要使用闭包?

闭包并不是一个固定的编程模式,而是一种工具。在哪些情况下使用闭包很有用呢?

封装变量

闭包可以用来封装函数中的变量,使其可以被隐藏和保护。通过闭包,我们可以创建私有变量和私有函数,从而防止全局作用域中的变量被其他函数修改。

-- -------------------- ---- -------
-------- --------------- -
  --- ----- - --
  -------- --------- -
    --------
    -------------------
  -
  ------ --------
-

--- -------- - ----------------
--- -------- - ----------------
----------- -- ----
----------- -- ----
----------- -- ----
----------- -- ----

在上面的代码片段中,我们定义了一个名为 createCounter 的函数,它返回一个闭包函数 counter。在 counter 函数内部,我们使用了外部变量 count,但是这个变量是私有的,只有闭包内部的函数能够修改和访问它。这样,我们就可以创建多个计数器,它们会独立地保持自己的计数状态,互不干扰。

条件延迟执行

闭包可以用来延迟函数执行,使得函数在合适的时机被调用。这种方式特别适用于需要等待某些条件满足后才能执行的函数。

-- -------------------- ---- -------
-------- ---------- -
  ------ ------------------ -
    -------------------- ------
  --
-

--------------------- -
  -------------- ------ ----------
---

在上面的代码片段中,我们定义了一个名为 wait 的函数,它返回一个闭包函数,其中调用了 setTimeout 函数。这个闭包函数实际上就是一个条件延迟执行的函数,它需要等待 time 毫秒后才会调用传进来的函数。我们可以使用它来实现定时器和节流等功能。

迭代器和生成器

闭包可以用来创建迭代器和生成器,从而按需生成序列化的数据。

-- -------------------- ---- -------
-------- -------------------- -
  --- ----- - --
  ------ ---------- -
    -- ------ -- ------------ -
      ------ -----
    -
    ------ --------------
  --
-

--- -------- - ------------------ -- -- ----
------------------------ -- ----
------------------------ -- ----
------------------------ -- ----
------------------------ -- ----
------------------------ -- -------

在上面的代码片段中,我们定义了一个名为 createIterator 的函数,它返回一个闭包函数作为迭代器。这个闭包函数内部维护了一个 index 变量,用来表示当前迭代位置。每次调用时,它会判断当前位置是否超出了序列长度,如果超出则返回 null,否则返回序列中下一个元素。

闭包的缺点

虽然闭包可以解决许多问题,但它也有一些缺点。

内存泄漏

由于闭包本质上是一个函数和它所在的词法作用域,所以当一个闭包被创建后,它的词法作用域中的所有变量都会被保留在内存中,直到闭包被销毁。如果闭包被创建后没有被及时销毁,它所使用的内存会一直被占用,容易造成内存泄漏。

在上面的代码片段中,我们定义了一个名为 createEventListener 的函数,它为指定节点添加点击事件监听器。事件监听器是一个闭包函数,它能够访问到 createEventListener 函数的作用域中的变量和函数。但是,当我们调用这个函数后,它所创建的闭包函数就会被添加到节点的事件列表中,而在这个函数被移除之前,它所引用的变量和函数都无法被销毁,容易导致内存泄漏。

性能问题

由于闭包涉及到函数和作用域链,所以当使用闭包时,函数调用的开销会比较大。尤其是在多次调用时,闭包的性能问题会更加明显。

-- -------------------- ---- -------
-------- --------------- -
  --- ----- - --
  ------ ---------- -
    ------ --------
  --
-

--- ------- - ----------------
--- ---- - - -- - - -------- ---- -
  ----------
-

在上面的代码片段中,我们定义了一个名为 createCounter 的闭包函数,用来创建一个计数器。在循环调用计数器的过程中,我们使用了闭包来实现。然而,由于闭包涉及到作用域链和函数调用的开销,所以在大量循环调用时,会导致性能问题。

总结

闭包是 JavaScript 中一项强大的语言特性,它可以让我们更加灵活地处理变量和函数的作用域。通过使用闭包,我们可以封装变量,延迟函数执行,创建迭代器和生成器,等等。不过,闭包也有一些缺点,如内存泄漏和性能问题。因此,在使用闭包时,我们需要注意它的使用方式和影响,并避免滥用。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64520fda675af4061b5bed19

纠错
反馈