这是我浏览器JavaScript for循环实现的作用域错误或泄漏,还是我的理解有误?
异常原因核心结论
这不是浏览器for循环的实现bug,也不是for循环本身存在作用域泄漏,是对JavaScript的两条基础作用域规则存在认知偏差:
- ES6 正式引入块级作用域之前,JavaScript 仅存在全局作用域和函数作用域两类作用域。for 循环、if 判断等用大括号包裹的代码块,本身不会生成独立的作用域。
- 所有没有通过
var/let/const显式声明的变量,会被自动挂载到全局对象上,成为全局可访问的全局变量,不受任何函数作用域限制。
对第一段代码的“符合预期”判断其实是巧合
之所以觉得第一段代码运行正常,是因为两个看似同名的变量根本不是同一个引用:
testFunc的形参H是绑定在testFunc上的局部变量,仅在testFunc内部可访问bFunc循环里的H没有任何显式声明,因此直接成为了全局变量,和testFunc里的局部H毫无关系,自然不会修改到testFunc内部的值
看到的“正常表现”,只是因为两个作用域的H根本不是同一个变量,并非JS的作用域隔离机制生效了。如果真的在bFunc里显式声明var H,这个H会成为bFunc自己的局部变量,同样不会影响testFunc;但如果省略声明,H就会变成全局的,和“子函数不能改父函数变量”的规则完全是两码事。
第二段代码的异常流程拆解
之所以觉得出现“子函数修改变量”的异常,本质是两个函数里用到的zzz全都是未显式声明的全局变量,从头到尾操作的都是同一个全局值:
- 因为
forFunc里的循环没给zzz加var/let声明,这个zzz直接成为全局变量 - 调用
forFunc后进入for循环,第一次迭代时全局zzz从0开始累加,弹出当前值 - 调用
cFunc时,其内部循环的zzz同样没有显式声明,操作的还是同一个全局变量,让它从0累加到循环终止值30 - 回到
forFunc的外层for循环时,此时全局zzz已经是30,不满足zzz<5的判断条件,直接跳出整个循环,最终弹出的zzz值为30,和预期的5完全不符
这个过程根本不存在“子函数穿透修改父函数局部变量”的情况——因为zzz从一开始就不是forFunc的局部变量,是所有函数共享的全局变量。哪怕在forFunc里通过var zzz把zzz声明成自己的局部变量,只要cFunc内部省略了变量声明,它操作的依然是独立的全局zzz,和forFunc的局部变量完全无关。
正确的认知与修复方式
首先要明确:for 循环本身从来没有过作用域泄漏的实现问题,所有异常都来自省略变量声明导致的全局变量污染:
- 如果遵循ES5规范,所有函数内的变量都要通过
var显式声明,将变量绑定到当前函数的作用域下,避免全局挂载 - 如果使用ES6及以上规范,循环变量直接通过
let声明,for 循环会自动为每次迭代生成独立的块级作用域,既不会让循环变量泄漏到外层,也不会被其他函数随意修改 - 开发时开启严格模式,所有未显式声明的变量会直接抛出语法错误,从根源上避免这类全局变量泄漏的问题
内容的提问来源于stack exchange,提问作者Martin Clemens Bloch
相关产品推荐
相关产品推荐

