addEventListener是否阻塞脚本执行?JS事件监听控制流答疑
addEventListener 事件注册的执行逻辑说明
首先明确核心判断:注册事件监听时脚本不会暂停等待事件触发,未触发的监听事件也完全不会阻塞脚本运行,具体的解释器执行逻辑可以拆解为以下几个环节:
- 注册动作本身是同步瞬时完成的
当JS解释器执行到addEventListener调用语句时,仅会完成一个同步操作:将传入的事件回调函数,存入对应DOM/对象对应事件类型的监听回调列表中,整个存储动作耗时极短,完成后会立刻按顺序执行addEventListener之后的下一行同步代码,全程不会停留等待事件触发。
你可以运行下面这段测试代码验证:
哪怕你永远不触发console.log('1. 脚本启动') window.addEventListener('customNeverTriggerEvent', () => { console.log('3. 永远不会触发的自定义事件回调执行了') }) console.log('2. 事件注册完成,这行日志会立刻输出')customNeverTriggerEvent这个自定义事件,控制台也会立刻打出1. 脚本启动和2. 事件注册完成,这行日志会立刻输出两条日志,不会有任何等待。 - 事件回调的调度完全依赖事件循环机制
JS是单线程运行的,靠事件循环调度所有异步任务:- 首先会按顺序把当前同步执行栈里的所有代码跑完,直到执行栈完全清空
- 执行栈清空后,事件循环才会轮询检查微任务队列、宏任务队列里有没有待执行的任务
- 你通过
addEventListener注册的回调,在事件真正触发前,只会存在于事件监听列表里,根本不会进入任务队列。只有对应事件被实际触发(比如用户点击、资源加载完成、自定义事件被dispatchEvent触发),对应的回调才会被推入宏任务队列,等执行栈空闲时才会被取出来执行。
- 未触发的监听不会产生阻塞
如果注册的事件始终没有触发,对应的回调就会一直留在监听列表中,仅占用极少量的内存存储引用,既不会进入执行栈,也不会进入任务队列,完全不会干预后续任何代码的执行流程。
注意不要混淆阻塞的来源:如果写完事件监听后发现后续代码不执行,问题永远出在
addEventListener之后的同步代码上——比如存在死循环、超长耗时的同步计算,这类代码会一直占住执行栈,不仅后续同步代码跑不了,连已经触发的事件回调也会被卡住没法执行,和事件注册动作本身没有关系。
举个典型的错误场景:document.getElementById('testBtn').addEventListener('click', () => alert('按钮被点击')) // 下面的死循环才是阻塞的真正原因 let i = 0 while (i < 10000000000) i++ console.log('这行要等上面的循环跑完才会输出,和事件监听无关')这段代码里就算你在循环跑的时候狂点按钮,弹窗也只会等循环跑完、执行栈清空之后才会弹出来。
内容的提问来源于stack exchange,提问作者Lorenzo Zabot
相关产品推荐
相关产品推荐

