JavaScript中函数作为参数传递时,词法作用域保持不变,因其定义时已绑定词法环境;函数携带[[Scope]]引用,变量查找始终依据定义位置而非调用位置,与this的动态绑定无关。

函数作为参数传递时,词法作用域依然保持不变——这不是靠“传参”实现的,而是因为函数在定义时就已牢牢绑定了它诞生时所处的词法环境。
函数携带自己的作用域链,不随调用位置改变
JavaScript 中的函数不是裸代码块,它内部隐含一个 [[Scope]](或称词法环境引用),记录了它被定义时能看到的所有外部变量。哪怕你把它当作参数传给另一个函数,甚至跨文件调用,这个绑定关系也不会断裂。
- 定义时决定作用域,调用时只决定 this 和执行上下文,不影响变量查找路径
- 比如
foo在全局定义,它内部访问的globalVar永远指向全局那个,不会因为你把它传进bar()就去读bar内部的同名变量 - 这和动态作用域语言(如 Bash 函数)完全不同:后者会按调用栈找变量,而 JS 始终按代码嵌套结构找
常见误区:以为“传进去”就“属于那里”
很多人看到 setTimeout(callback, 100) 或 button.addEventListener('click', handler) 就误以为 callback 或 handler 现在归事件系统“管”了,作用域也跟着转移。其实完全相反:
-
handler仍能访问它定义时所在函数里的let count = 0,哪怕事件发生在几秒后、DOM 深层节点上 - 如果
handler是在循环中动态创建的(比如for (let i of arr) { btn.onclick = () => console.log(i); }),它记住的是每次迭代中独立的i绑定——这正是词法作用域 + 块级作用域共同保障的 - 若用
var替代let,因变量提升和函数作用域,所有 handler 会共享同一个i,暴露的其实是作用域规则没被正确理解,而非“传参导致作用域丢失”
闭包是这种保持机制的自然结果
当你把一个内部函数作为返回值或参数传出,只要它引用了外层变量,JS 引擎就会让那些变量持续存活——不是复制值,而是维持引用。这就是闭包的本质:
立即学习“Java免费学习笔记(深入)”;
- 函数定义 → 记住外层词法环境 → 即使外层函数执行结束,环境仍保留在内存中
- 传参只是把函数值(即该函数对象的引用)交给另一个地方,不触发环境重绑定
- 典型例子:
const makeAdder = x => y => x + y;,makeAdder(5)返回的函数永远记得x === 5,无论你把它传给谁、在哪里调用
调试时怎么确认作用域没变?
可以在函数内部打个断点,打开浏览器开发者工具的 “Scope” 面板,你会清楚看到当前作用域链里包含哪些词法环境(Local、Closure、Global),其中 Closure 下列出的正是它定义时捕获的外部变量。这个列表不会因为你是在 map() 回调里还是 setTimeout 里调用它而改变。


















