SDET / Frontend · Coding

TikTok var/let 与 setTimeout:循环绑定和循环外访问

逐步解释同一var绑定、每次let绑定,以及换let后外部log抛错但已注册回调仍可执行。

题目重点:逐步解释同一var绑定、每次let绑定,以及换let后外部log抛错但已注册回调仍可执行。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练片段for(var i=0;i<5;i++){setTimeout(()=>console.log(i),100)};console.log(i),回调异步晚于当前同步脚本。
  • 不存在外层同名i;换let后最后循环外访问i会抛ReferenceError。

解题路线

  1. var的i在此作用域只有一个绑定,五个闭包读取同一变量而非定义当时的值。循环同步完成后i=5,所以外部console先输出5,随后五个timer各读到5。
  2. setTimeout的100ms不是保存变量快照,也不保证刚好100ms执行;它只登记未来任务,当前同步工作不会因此暂停。核心是回调执行时读取闭包环境。
  3. 换成for(let i...)时每次迭代有独立循环绑定,回调分别读取0、1、2、3、4。但循环外i不在作用域,console.log(i)先抛ReferenceError,不能声称外部仍输出5。
  4. 脚本异常不会自动撤销此前登记的浏览器定时器,因此已注册的五个回调通常仍会执行;如果宿主因未捕获异常退出进程则环境行为不同,要区分浏览器与命令行。
  5. 如果仍需var,可用IIFE参数或独立函数参数捕获当次值;避免写全局snapshot变量,那仍会被下一轮覆盖。默认参数、任务队列与闭包语义需按具体片段核对。
  6. 测试将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顺序怎样?
  • 宿主未捕获异常策略为何影响观察?
✓已复制微信号!