简单了解 JavaScript 闭包

闭包是 JavaScript 中一个非常重要的,而且是其中最具特色的概念之一。让我们一起了解一下什么是闭包以及它为什么如此重要。

什么是闭包?

根据 MDN 对于闭包的解释:

闭包 (closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

闭包的实质是一个函数可以访问其外部作用域的变量,即使这个函数在其外部作用域已经退出。这种特性使得闭包能够在函数调用之间保留状态,实现跨函数的数据传递。

javascript 复制代码
function outer() {
  var outerVar = '外部作用域的变量'

  function inner() {
    console.log(outerVar) // inner 函数可以访问 outer 函数中的变量
  }

  return inner
}

var closureFunc = outer()
closureFunc() // 输出 "外部作用域的变量"

在上面的例子中,inner 函数形成了一个闭包,它可以访问 outer 函数中的 outerVar 变量,即使 outer 函数已经执行完毕并且 outer 函数的执行上下文已经出栈。这是因为 inner 函数继续保持对 outer 函数作用域的引用,这就是闭包的本质。

闭包的工作原理

作用域链(Scope Chain)

要理解闭包的工作原理,需要了解 JavaScript 的作用域链机制。

JavaScript 中的作用域链是一个从函数内部到外部作用域的链式结构。当一个函数内部引用外部变量时,JavaScript 引擎会从内部函数开始,沿着作用域链向上查找,直到找到该变量的定义为止。作用域链的查找顺序是:首先查找函数内部的局部变量,然后是其外部祖先函数的局部变量,最后是全局作用域。

变量提升(Variable Hoisting)

JavaScript 中,变量提升是指变量在声明后,其作用域会向上提升至最近的函数外部作用域。这意味着即使在一个函数内部声明了一个外部变量,JavaScript 引擎也会在函数外部为其创建一个全局变量。因此,当内部函数引用了外部变量时,实际上是在引用全局变量。

闭包的实现

闭包的实现依赖于作用域链和变量提升。当一个内部函数引用了外部变量时,JavaScript 引擎会在内部函数中创建一个对该外部变量的引用。当内部函数执行完毕,其作用域消失,但内部函数对外部变量的引用仍然存在。此时,如果外部函数继续执行,其作用域链中的外部变量会被提升,从而使得内部函数引用的外部变量得以保留。

闭包的实际应用

封装数据

一个常见的用途是通过闭包创建私有变量,以实现数据的封装和保护。这有助于防止其他代码意外地修改或访问数据。

javascript 复制代码
function createCounter() {
  var count = 0

  return {
    increment: function () {
      count++
    },
    decrement: function () {
      count--
    },
    getValue: function () {
      return count
    },
  }
}

var counter = createCounter()
counter.increment()
counter.increment()
console.log(counter.getValue()) // 输出 2

在这个示例中,count 变量是私有的,只能通过返回的对象中的方法进行访问和修改,从而确保数据的封装性和隐私性。

延迟执行

闭包还可以用于创建延迟执行的函数,例如在事件处理程序中。

javascript 复制代码
function createDelayedFunction(message, delay) {
  return function () {
    setTimeout(function () {
      console.log(message)
    }, delay)
  }
}

var delayedFunc = createDelayedFunction('延迟消息', 2000)
delayedFunc() // 2 秒后输出 "延迟消息"

在这里,createDelayedFunction 函数返回一个闭包,它可以延迟执行一段代码,以便在指定的时间后输出消息。

参考资料

相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试