为何setTimeout和DOM API执行顺序不同?JS WebAPI运行机制疑问
首先纠正核心认知偏差:
你对
setTimeout的执行流程理解是正确的,但存在一个普遍误区:并非所有浏览器提供的Web API都是异步、需要走回调队列调度的,Web API根据设计目标分为同步、异步两大类,二者执行逻辑完全不同。你可以把同步WebAPI理解为浏览器用原生代码实现的普通JS函数,调用后立刻执行返回结果,和你自己写的普通JS函数执行逻辑没有本质区别,只是内部实现不是JS编写的而已。
1. 为什么DOM API没有遵循setTimeout的执行模式?
setTimeout属于异步调度类Web API,设计目的是推迟回调的执行时机,所以才会走「WebAPI计时->回调入队->主线程空闲后执行」的流程。
而document.querySelectorAll属于同步DOM查询API,设计目的是实时返回当前DOM树的查询结果,调用后会直接在主线程同步执行得到结果,不需要异步调度,自然和setTimeout的执行模式不同。
2. 如果DOM API是在独立线程处理的,为什么它的输出没有出现在End之后?
这个问题的前提假设不成立:绝大多数同步DOM读写API都不是在独立线程处理的。
DOM树是浏览器主线程管控的核心资源,为了避免多线程读写引发的并发冲突,所有同步DOM操作都会直接在主线程执行,不会交给其他线程处理,调用后会立刻得到返回值,所以输出自然会在后续的console.log('End')之前。
3. document.querySelectorAll的执行流程是否为「document.querySelectorAll -> WebAPI -> 回调队列 -> 主线程」?
不是,它的实际执行流程非常简单:
- 主线程执行到
document.querySelectorAll('img')调用 - 直接在主线程遍历当前DOM树,匹配所有img节点生成NodeList
- 当场返回结果给
console.log输出 - 主线程继续执行下一行的
console.log('End')
全程没有跨线程调度,也不会进入任何回调队列。
4. DOM API是否需要经过回调队列、等待主线程空闲才能执行?如果是,为什么它的输出顺序和setTimeout不同?
只有异步DOM API(比如DOM事件回调、requestAnimationFrame、MutationObserver回调等)才会涉及到事件循环的队列调度,需要等待主线程空闲后执行。
而document.querySelectorAll这类同步DOM API不需要走回调队列,会直接在当前执行栈中同步运行,所以输出顺序和普通同步代码一致,和setTimeout这种需要入队等待的异步回调自然不同。
内容的提问来源于stack exchange,提问作者Waleed Ahmad

