【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
标题
202、【Agent】【OpenCode】JS 语法:闭包捕获还是参数传递
背景
上篇 blog
【Agent】【OpenCode】JS 语法:setTimeout 七种常用形式
盘点了 setTimeout 的七种常用形式,本质是三个自由度的组合:回调怎么写(箭头内联 / 函数引用 / 递归)、数据怎么给(闭包捕获 / 第三参数快照 / 不喂)、生命周期怎么管(clearTimeout / unref / 延迟 0)。其中形式①"内联箭头 + 闭包"与形式②"函数引用 + 第三参数"恰好是给回调喂数据的两种相反方式——一个靠闭包实时引用,一个靠参数注册时快照。本篇专门展开这两种方式的取舍:什么时候可以用闭包,什么时候必须传参
OpenCode
先看两种写法的直观对比:
// 形式①:闭包捕获(实时引用)for(vari=0;i<3;i++)setTimeout(()=>console.log(i),0)// 3 3 3// 形式②:第三参数传值(注册时快照)for(vari=0;i<3;i++)setTimeout(console.log,0,i)// 0 1 2🧩核心差异:实时引用 vs 值快照
- 闭包捕获的是"变量本身":回调执行那一刻才去读变量,看到的是"当时的最新值"
- 传参传的是"当时的快照":基本类型拷贝值,注册那一刻就固化
这个差异是后面所有取舍准则的根源。
🧩什么时候用闭包
| 条件 | 说明 |
|---|---|
| ① 变量就在回调定义处的作用域内 | 能直接看见,就能直接捕获 |
| ② 要读"当前值" | 变量在回调执行前不变,或就是要最新值 |
| ③ 回调一次性内联 | 比如延迟一次就执行的 setTimeout |
| ④ 无循环/复用/生命周期顾虑 | 不涉及"每轮各取一值""长期持有"等场景 |
本案例为什么用闭包(200 篇的thread.ts:197-199):
setTimeout(()=>{client.call("checkUpgrade",{directory:cwd})},1000)client/cwd就在 handler 闭包作用域内(条件①);回调一次性、执行于启动后 1 秒,要的就是"当时的 client/cwd"(条件②③);无循环、无复用(条件④)。四个条件全满足,闭包是自然选择。
🧩什么时候必须传参
| 条件 | 说明 |
|---|---|
| ① 变量不在回调定义的作用域 | 是别的函数/模块的局部,回调定义处看不见 |
| ② 需要"值快照" | 循环里每个定时器要各自的值(见上方 var 陷阱输出) |
| ③ 回调要独立复用 / 可传递 | 回调是单独命名函数、交给别的模块,只能靠参数注入 |
| ④ 怕闭包"钉住"作用域泄漏 | 闭包持有整个作用域链;长期存活回调捕获大对象会滞留内存 |
传参写法对照:
// 用第三参数喂数据(效果等价于闭包版,但更啰嗦)setTimeout((cli,dir)=>cli.call("checkUpgrade",{directory:dir}),1000,client,cwd)📊取舍准则汇总
| 维度 | 用闭包 | 必须传参 |
|---|---|---|
| 变量可见性 | 在回调定义作用域内 | 在作用域外 |
| 取值时机 | 执行时实时读取 | 注册时固化快照 |
| 回调形态 | 一次性内联 | 独立复用/可传递 |
| 生命周期 | 无长引用顾虑 | 怕闭包钉住大作用域 |
📌一句话记忆
变量在作用域内 + 要实时值 + 一次性内联 → 闭包;变量在作用域外 + 要值快照 + 回调要复用/传递 + 怕长引用泄漏 → 传参。200 篇的
setTimeout回调四个条件全满足闭包,所以写成() => client.call(...)而非setTimeout(fn, 0, args)。
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog