You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dispatchEvent() method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().
提问
▼ 请问点击哪个按钮会导致页面卡死?
▼ 请问点击按钮红色 div 会闪吗?
题目比较简单,相信大家都有答案了。
我们继续往下。
开始之前
对于 Event Loop,相信大家都有这样一张图:
接下来,将会更深入地了解 Event Loop,真的如上图所示吗?
没错,我也是从以下链接受益,并结合自己的理解,将其写下来而已。
为什么 JavaScript 设计成单线程?
最初 JavaScript 是为浏览器而设计的,旨在增强可交互性。
单线程,意味着同一时间只能做一件事情。
设想一下,有两个线程同时作用于某个元素,一个是修改样式,另一个是删除元素,如何响应呢?引入锁机制?
当时网页不如现在复杂,选择单线程是明智、合理、够用的,操作变得有序可控,且大大降低复杂度。
随着时代的发展,计算越来越复杂,单线程有点捉襟见肘,后来 HTML5 提供了 Web Worker 等 API 可主动创建新的线程运行一些复杂的运算。
什么是 Event Loop?
规范是这样定义的:
个人理解:它是让各种任务有序可控的一种机制。
用伪代码表示:
它是无限循环的,7 × 24h 随时待命,直至浏览器 Tab 被关闭。
只要有任务,它就会不停地从队列中取出任务,执行任务。
什么是 Task?
规范是这样定义的:
简单来说,任务就是一个包含 steps 等属性的对象,里面记录了任务的来源、所属 Document 对象、上下文等,以供后续调度。
常见的任务有:
什么是 Task Queue?
常规意义的队列
队列(Queue)是一种基本的数据结构,遵循先进先出(FIFO, First In First Out)的原则。在队列中,最先插入的元素最先被移除,类似于排队等候的场景。
Event Loop 中的任务队列
规范中提到:
前面提到,task 是有 source 的,比如来自鼠标点击等。排队时,同 source 的 task 会被放入与该 source 相关的 task queue 里。假设鼠标事件的任务要优于其他任务,Event Loop 就可以在对应 source 的 task queue 中取出任务优先执行。规范里 Event Loop 执行步骤并没有明确定义“出队”的规则,它取决于浏览器的实现。
现在 Event Loop 用伪代码表示是这样的:
什么时候重绘页面?
总不能只执行任务,不更新 DOM 吧。
本质上,网页就是给人看的,与人交互的,所以用户体验非常重要。假设任务队列有源源不断的任务产生,如果 Event Loop 只会一直循环执行队列里的任务,而不去更新页面,用户体验是非常糟糕的。
请问浏览器什么时候会更新页面?
浏览器是非常聪明的,没必要的工作它不会做。以 60Hz 屏幕为例,每秒刷新 60 次,约 16.7ms 刷新一次。只要满足该刷新频率的,显示就算是流畅的,因为再快的刷新频率对肉眼来说也不会有明显的感知。也就是说每 16.7ms 可获得一次渲染机会(rendering opportunity),这样浏览器就知道要更新 DOM 了。
因此,一个任务执行完,如果有渲染机会先更新 DOM,接着才执行下一个任务。
现在 Event Loop 用伪代码表示是这样的:
什么是 Microtask?
还没完,还没完...
规范中提到:
好,我们重新捋一下:
为便于区分理解,本文暂且将以下规范术语口语化(但注意,这种说法不一定准确)。
有哪些微任务?
在 JavaScript 里会产生微任务的大概有:
什么时候执行微任务?
从规范(Processing model)可知,只要 Event Loop 存在,就必须不断执行以下步骤:
至此,文章开头的提问之一就有答案。由于它在执行微任务的过程中不停地产生新的微任务,因此将会在 3.iii 陷入死循环,自然页面就“卡死”了。
跟 task 的一些区别
请注意,无论是(宏)任务,还是微任务,执行过程中都可能产生“新”的(宏)任务或微任务。它们的执行顺序是有区别的:
现在 Event Loop 用伪代码表示是这样的:
什么是 requestAnimationFrame?
噢,还没完,还有一个 requestAnimationFrame,其回调函数会在页面重绘之前调用。
当浏览器检测到有渲染机会,会更新 DOM,具体执行顺序如下:
除了有 task queue(集合)、microtask queue(队列),还有一个 animation frame callbacks,它是一个 ordered map(映射)。
同样地,执行 callbacks 的过程中产生新的 callback,它们会放到下一次 Loop 执行,这点跟微任务是不一样的。
现在 Event Loop 用伪代码表示是这样的:
Node.js Event Loop 是怎样的呢?
相比之下,Node.js 里没有以下这些:
Node.js 特有的是:
Node.js 的 Event Loop 由 libuv 实现,包含以下阶段:
在 Node.js 中,还有一个特殊的
process.nextTick()
方法。技术上,它不属于事件循环的一部分。当你在某个阶段调用时,传递给它的所有回调将在当前阶段执行完之后,下一个阶段执行之前执行。如果递归调用它,是会造成死循环的。用伪代码表示是这样的:
Worker Event Loop 又是怎样的呢?
它更简单:
且线程之间相互独立,每个线程都有自己的 Event Loop,互不干扰。
现在 Event Loop 用伪代码表示是这样的:
但注意,如果在 Web Worker 的线程向主线程传递消息,这个消息对于 Window Event Loop 来说属于一个 task,它仍受主线程的 Event Loop 控制,该排队还得排队。
思考题
先回到文章开头的题目。
点击哪个按钮会导致页面卡死?
答案:whileLoop、promiseLoop 会导致页面卡死,timerLoop 则不会。
whileLoop 分析:点击按钮,产生一个 task,进入 task queue 排队。轮到它的时候,执行 whileLoop() 方法,里面是一个无线循环的 while 语句,因此这个 task 会一直执行下去,且致使后面的 task、更新 DOM 等永远无法执行。页面就卡死了。
timerLoop 分析:点击按钮,产生一个 task,进入 task queue 排队。轮到它的时候,执行 timerLoop() 方法,又产生一个 task 并放入 task queue。执行完之后,如果有 rendering opportunity 会先更新 DOM,完了执行进行下一轮。尽管 timerLoop 里不停地产生新的 task,但用户仍然通过文本选择、页面滚动等产生其他 task 进入到 task queue 进行排队。因此页面是不会呈现卡死状态的。
promiseLoop 分析:点击按钮,产生一个 task,进入 task queue 排队。轮到它的时候,执行 promiseLoop() 方法,其中 Promise.resolve() 产生一个 microtask 并放入 microtask queue。当 task 执行完,接着从 microtask queue 里取出 microtask 执行,即执行 then(promiseLoop),它有又产生新的 microtask,所以 microtask queue 就一直有任务存在,因此会陷入死循环,致使后面的 task、更新 DOM 等永远无法执行。
它们会闪烁吗?
你有没有担心过这些代码会“闪”一下?
请问点击按钮红色块会闪烁吗?
CodePen
分析:上述点击事件产生一个 task(事件回调),只有执行完 task 里面的代码,才会执行后面的微任务或更新 DOM。也就是说渲染之前,实际只有最后一行的样式设置是起作用的,不管中间设了多少遍,浏览器只关心最后的样式如何。
它们的执行顺序是?
以下示例,一个按钮绑定了两个 click 事件:
现触发 click 事件的方式有两种:一个是通过鼠标点击触发,另一个是通过
btn.click()
触发。这两种方式的执行顺序一样吗?通过鼠标点击的结果是:
通过
btn.click()
的结果是:原因分析:通过与用户交互而触发的事件,其监听器是异步调用的,而通过
btn.click()
触发,会同步派发事件,并以合适的顺序同步地调用监听器。对于“鼠标”点击:由于 btn 注册了两个 click 监听器,鼠标点击一次,产生两个 task 进入 task queue,先后执行,因此得到前面的结果。
对于
btn.click()
模拟点击:当执行到btn.click()
时,按顺序同步执行两个监听器。References
The text was updated successfully, but these errors were encountered: