JavaScript 中的闭包和作用域链

在 JavaScript 中,闭包和作用域链是非常常见和重要的概念。了解它们,对于编写高效、可读、可维护的代码都非常有帮助。本文将会介绍闭包和作用域链的概念、原理、使用场景,并通过示例代码来解释它们的应用。

闭包的概念和原理

闭包是指能够访问自由变量(外部环境的变量)的函数。换句话说,闭包就是一个函数,它可以“记住”在函数创建时它的外部环境中的变量,并在需要时继续使用这些变量。由于闭包能够访问外部环境的变量,所以它们可以充当回调函数、函数工厂、模块化编程等一系列编程技术的基础。

闭包的实现原理是 JavaScript 中的作用域链。在 JavaScript 中,每个函数都有一个“执行环境”,而每个执行环境都有一个与之对应的“作用域链”。在函数创建时,其作用域链中会包含创建该函数的外部环境的变量对象,这个变量对象即为所谓的“自由变量”。当函数需要访问环境变量时,其会沿着作用域链逐级向上查找,直到找到距离它最近的一个包含该变量的变量对象为止。

闭包的使用场景

闭包的使用场景非常广泛。下面列举了几个常见的用例。

一、回调函数

当我们使用异步编程技术(如 Ajax、setTimeout 等)时,常常需要传递回调函数。假设我们需要实现一个计数器,每隔一段时间就会执行一次回调函数。

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

此时,我们需要传递一个回调函数作为参数。如果我们希望每次调用回调函数时,它都能访问计数器的上下文环境,那么我们可以使用闭包。

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

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

这里,我们将 i 传递给回调函数,实现了闭包。

二、函数工厂

当我们需要创建多个具有相同功能的函数时,我们可以使用函数工厂。函数工厂仍然是一个函数,但它输出的不是单一的函数,而是一组具有相同属性的函数。在工厂函数内部,我们可以使用闭包来实现其中的一些功能。

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

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

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

这里,我们使用 createAdder 函数创建了两个函数对象 add5 和 add10。它们都通过闭包记住了 createAdder 函数的参数 num,并在调用时将其加上传入的参数。

三、模块化编程

当我们需要将程序按照功能分解成多个独立的模块时,我们可以使用模块化编程。在这种情况下,每个模块都应该具有自己的命名空间,且不应该与其他模块产生干扰。此时,我们可以使用闭包来实现模块的封装。

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

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

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

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

这里,我们使用了一个立即执行函数来创建模块 myModule。模块内部使用闭包来保护 privateVar 变量,以及访问 privateVar 的函数 privateFunc。同时,我们通过返回一个包含 publicFunc 函数的对象,将其公开为模块的 API。

作用域链的概念和原理

作用域链是 JavaScript 中函数与变量之间的连接关系。当一个函数被调用时,它的执行环境和作用域链就被创建了。在作用域链上,有一个全局变量对象 window,它是整个作用域链的顶端。接下来,每个执行环境都会创建一个变量对象,用于存放当前执行环境中定义的变量和函数。这些变量对象被链接成一个“作用域链”。

当函数需要访问某个变量时,它会首先在自己的变量对象中查找符合条件的变量,如果找不到,则继续在自己的上级作用域链中查找。直到找到该变量为止,或者到达作用域链的顶端(全局变量对象 window)。

作用域链的使用场景

作用域链的使用场景,可以说是几乎涵盖了所有 JavaScript 中的函数和变量使用。下面,我们来介绍一些常见的使用场景。

单例模式

单例模式是指整个程序中只有一个实例对象的模式。一个简单的单例模式实现如下所示。

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

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

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

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

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

其中,我们使用了闭包来实现对 instance 变量的保护,并使用了作用域链来保证函数内部的变量与外部的变量不会相互干扰。

DOM 操作

在 JavaScript 中,DOM 操作时非常常见的操作。我们经常需要访问某个元素的父元素、子元素或者兄弟元素。这些元素都是通过作用域链被链接到一起的。

当我们需要向子元素中添加一个事件监听器时,可以使用作用域链来访问它的父元素或者其他元素。

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

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

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

这里,我们使用作用域链访问了 myDiv 元素,并给它添加了一个点击事件监听器。

闭包和作用域链的注意事项

当我们在使用闭包和作用域链时,需要注意一些细节:

  • 变量的生命周期不会因为执行环境结束而结束。当闭包中引用了某些变量,这些变量会一直存在于内存中,直到闭包本身被释放。
  • 闭包会保护它自己的作用域链。这意味着在闭包内部定义的变量和函数不能被外部访问到。
  • 多个闭包引用同一个变量时,它们都会共享这个变量的值。如果其中一个闭包改变了这个变量的值,其他闭包也会受影响。

结论

本文主要介绍了 JavaScript 中的闭包和作用域链。通过对闭包和作用域链的原理、使用场景和注意事项的介绍,我们可以更好地理解 JavaScript 中函数和变量之间的链接关系。同时,这也可以帮助我们编写更高效、可读、可维护的代码。

参考文献

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6708eaf3d91dce0dc8753597