题目重点:逐步解释同一var绑定、每次let绑定,以及换let后外部log抛错但已注册回调仍可执行。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练片段for(var i=0;i<5;i++){setTimeout(()=>console.log(i),100)};console.log(i),回调异步晚于当前同步脚本。
- 不存在外层同名i;换let后最后循环外访问i会抛ReferenceError。
解题路线
- var的i在此作用域只有一个绑定,五个闭包读取同一变量而非定义当时的值。循环同步完成后i=5,所以外部console先输出5,随后五个timer各读到5。
- setTimeout的100ms不是保存变量快照,也不保证刚好100ms执行;它只登记未来任务,当前同步工作不会因此暂停。核心是回调执行时读取闭包环境。
- 换成for(let i...)时每次迭代有独立循环绑定,回调分别读取0、1、2、3、4。但循环外i不在作用域,console.log(i)先抛ReferenceError,不能声称外部仍输出5。
- 脚本异常不会自动撤销此前登记的浏览器定时器,因此已注册的五个回调通常仍会执行;如果宿主因未捕获异常退出进程则环境行为不同,要区分浏览器与命令行。
- 如果仍需var,可用IIFE参数或独立函数参数捕获当次值;避免写全局snapshot变量,那仍会被下一轮覆盖。默认参数、任务队列与闭包语义需按具体片段核对。
- 测试将timer注入为队列,先跑全部同步语句再逐个回调核对输出;分别检验var、let回调和循环外ReferenceError,而不是靠真实睡100ms观察偶然顺序。
手动走一遍
var片段输出六个5:第一个来自循环外同步log。let片段循环外不是5而是ReferenceError;捕获该异常后,已登记的回调输出0至4。异步回调不是循环中即时执行。
规模与取舍
n个注册与回调O(n),每轮闭包/任务共O(n)存储;等待时间不是算法CPU复杂度。
易错点与追问
容易失分的边界
- let替换后仍预测外部log=5。
- 把timer延迟当变量值冻结。
- 用共享snapshot变量代替独立绑定。
继续深挖
- 放进函数作用域var有何改变?
- Promise微任务与timer顺序怎样?
- 宿主未捕获异常策略为何影响观察?
