javascript闭包如何使用_它怎样影响变量的作用域【教程】

8次阅读

javaScript闭包使外部函数变量在调用后仍存活,因其内部函数记住了创建时的词法环境;只要内部函数存在,就能持续访问外部变量,依赖词法作用域与函数对象携带环境,而非引用计数。

javascript闭包如何使用_它怎样影响变量的作用域【教程】

闭包是怎么让外部函数变量在调用后依然存活的

javascript 闭包的本质,是内部函数记住了它被创建时所处的词法环境。只要这个内部函数还存在(比如被返回、被赋值给变量、被传入事件监听器),它就能持续访问外部函数的变量,哪怕外部函数早已执行完毕。

关键点在于「词法作用域」+「函数对象携带环境」,不是靠引用计数或特殊标记。这意味着:var 声明的变量、let/const 声明的块级绑定,都能被闭包捕获,但行为略有差异(后者会为每次循环迭代创建独立绑定)。

  • 常见错误现象:for (var i = 0; i console.log(i), 100) 输出三个 3 —— 因为所有回调共享同一个 i 变量,循环结束时 i === 3
  • 修复方式:改用 let i(每次迭代新建绑定),或用立即执行函数包裹 var i,或用 setTimeout 的第三个参数传参
  • 性能影响:闭包会阻止外部函数作用域被垃圾回收,如果闭包长期存在且引用了大对象(如 dom 节点、大型数组),可能引发内存泄漏

如何正确返回一个带状态的闭包函数

最典型的闭包使用场景是封装私有状态。你不需要暴露变量本身,只暴露能操作它的函数。

function createCounter() {   let count = 0;   return {     increment: () => ++count,     reset: () => count = 0,     value: () => count   }; }  const counter1 = createCounter(); console.log(counter1.value()); // 0 counter1.increment(); console.log(counter1.value()); // 1
  • 每个 createCounter() 调用都生成独立的 count 绑定,counter1counter2 互不影响
  • 不能直接访问 counter1.count —— 它根本不存在于对象上,只存在于闭包环境中
  • 如果返回的是单个函数(如 return () => count++),那它只能做单一操作;返回对象更灵活,但也意味着要管理多个闭包引用同一环境

闭包和 this 绑定容易混淆的点

闭包捕获的是词法作用域里的变量,不包括 thisthis 是运行时决定的,和函数在哪定义无关。所以常有人误以为「闭包能保存 this」,其实不能。

立即学习Java免费学习笔记(深入)”;

  • 错误写法:function foo() { const self = this; return function() { console.log(self.bar); } } —— 这里保存的是 self,不是 this;真正保存 this 需显式绑定
  • 正确做法:用箭头函数(自动继承外层 this)、bind、或在调用前缓存 const that = this
  • vue/react 类组件中,事件回调常用 handleClick = () => {...} 就是为了避免 this 丢失,这背后也依赖闭包捕获类实例上下文

什么时候不该用闭包——以及替代方案

闭包不是银弹。当逻辑简单、状态无需隔离、或需要频繁创建大量闭包时,它反而增加理解成本和内存开销。

  • 避免在循环中无节制地创建闭包:比如 list.forEach(item => element.addEventListener('click', () => doSomething(item))),如果 list 很大,每个监听器都是独立闭包;可改用事件委托 + datasetEvent.target
  • 模块化代码时,es6 模块本身已提供作用域隔离,不必强行套一层闭包(如 (function(){...})()
  • 现代开发中,class 实例属性 + 方法也能实现类似私有状态的效果(虽然 js 目前仍靠 #private 字段真正私有),比手动闭包更直观

闭包真正的不可替代性,在于需要「多个函数共享同一份私有状态」且「这份状态不能挂载到对象属性上」的场景——比如防抖函数的定时器 ID、柯里化函数的预置参数、或 Web Worker 通信中的回调队列管理。这些地方,绕不开闭包的环境捕获能力。

text=ZqhQzanResources