DevTools Call Stack vs Scope
比较 DevTools 暂停状态下 Call Stack 与 Scope 的职责:前者解释控制流如何到达当前 frame,后者解释当前执行上下文可访问的值与绑定。
#synthesis
#growing
#frontend
#browser
#chrome-devtools
[!info] related notes
- 所属 MOC: DevTools Sources MOC
- 前置概念: 执行上下文, 调用栈和执行上下文的关系
- 操作入口: 断点调试
- 面板: Sources 面板
DevTools Call Stack vs Scope
一句话关系
Call Stack
→ 控制流从哪里来
Scope
→ 当前 frame 能看到什么值
一个解释路径,一个解释状态。
Call Stack
当执行暂停时,每个 frame 对应一次尚未返回的函数调用/执行上下文。
从当前 frame 向上可以回答:
- 当前函数是谁调用的;
- 上一层参数从哪里来;
- 某事件/请求/回调经过了哪些业务函数;
- 异常怎样一路冒泡到这里。
切换不同 frame 后,Scope 也会跟着改变。
Scope
Scope 展示选中 frame 在该暂停时刻可访问的绑定,例如:
- local;
- closure;
- script/module;
- global;
this。
它适合回答:
这一刻输入已经错了吗?还是在当前函数里才变错?
两者怎样配合
假设最终请求 body 里的 userId 错了:
暂停在 fetch 前
→ Scope: userId 已经错
→ Call Stack: 上一层谁传进来的
→ 切到 caller frame
→ Scope: caller 中原始值是否正确
这样可以逐层把错误边界向上收缩。
异步边界
同步调用栈会在一次 task 结束后清空。
Promise、timer、event listener 的后续 callback 属于新的执行时机。
DevTools 可能展示 async stack/context 来帮助连接来源,但这不意味着 JavaScript 物理上一直保留同一条同步 stack。
所以调试 async bug 时,还要结合:
- event loop;
- Promise chain;
- task/microtask;
- Network/initiator。
Scope 不是历史记录
Scope 展示暂停时刻的当前绑定,不会自动告诉你:
- 这个值之前是什么;
- 谁在 2 秒前修改了它;
- 哪次 render 导致变化。
需要通过:
- 更早的 breakpoint;
- conditional breakpoint;
- watchpoint/DOM breakpoint;
- logging/trace;
把变化过程捕获下来。
学习检查
- Call Stack 与 Scope 分别回答什么问题?
- 为什么切换 Call Stack frame 后 Scope 会变化?
- Scope 中发现值已经错误,下一步为什么通常要看 caller frame?
- 为什么 Promise callback 的 async stack 不能被理解成一直存在的同步调用栈?
- Scope 为什么不能直接回答“谁之前改了这个变量”?
- 给一个三层函数调用,把错误值从当前 frame 追溯到最早污染点。