JS进阶:事件循环与代理

学习自:JS忍者秘籍

事件循环

除了事件,还要保持浏览器执行的其他操作。这些操作被称为任务,并且分为两类:宏任务(或通常称为任务)和微任务。事件循环的实现至少应该含有一个用于宏任务的队列和至少一个用于微任务的队列。两种队列在同一时刻都只执行一个任务

事件循环基于两个基本原则:
● 一次处理一个任务。
● 一个任务开始后直到运行完成,不会被其他任务中断。

宏任务的例子很多,包括创建主文档对象、解析HTML、执行主线(或全局)JavaScript代码,更改当前URL以及各种事件,如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,宏任务代表一个个离散的、独立工作单元。运行完任务后,浏览器可以继续其他调度,如重新渲染页面的UI或执行垃圾回收。

而微任务是更小的任务。微任务更新应用程序的状态,但必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。微任务的案例包括promise回调函数、DOM发生变化等。微任务需要尽可能快地、通过异步方式执行,同时不能产生全新的微任务。微任务使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使应用程序的状态不连续。

注意处理宏任务和微任务队列之间的区别:单次循环迭代中,最多处理一个宏任务(其余的在队列中等待),而队列中的所有微任务都会被处理。所有微任务会在下一次渲染之前执行完成,因为它们的目标是在渲染前更新应用程序状态。

如果要达到每秒60帧(60 fps)的速度,理想情况下,单个任务和该任务附属的所有微任务,都应在16ms内完成。

事件监测和添加任务是独立于事件循环的,尽管主线程仍在执行,仍然可以向队列添加任务。

请注意事件处理函数的发生频率以及执行耗时。例如,处理鼠标移动(mouse-move)事件时应当特别小心。因为移动鼠标将导致大量的事件进入队列,因此在鼠标移动的处理函数中执行任何复杂操作都可能导致Web应用的糟糕体验。

setTimeout和setInterval

当JavaScript忙于执行时,在浏览器上的用户交互会变得迟钝,甚至无响应。由于当JavaScript执行时,重新渲染页面的更新都被暂停,浏览器将会卡顿,看起来似乎处于假死状态。

计时器能够有效地中止一段JavaScript的执行,直到一段时间之后,还可以把代码的各个部分分解成片段,这些片段的执行消耗时间不足以导致浏览器挂起。

这些方法都是挂载在window对象(全局上下文)的方法。与事件循环类型不同,这些方法不是JavaScript本身定义的,而是由宿主环境提供的(如浏览器或Node.js)

如果队列中已经存在一个间隔计时器,那么就不会创建一个新的间隔计时器。

计时器提供一种异步延迟执行代码片段的能力,至少要延迟指定的毫秒数。因为JavaScript单线程的本质,我们只能控制计时器何时被加入队列中,而无法控制何时执行

事件代理

DOM是元素的分层树,发生在一个元素(target)上的事件通常是通过DOM进行代理的,有以下两种机制。
1.捕获——首先被顶部元素捕获,并依次向下传递。
2.冒泡——事件首先通过捕获,从顶部元素传递到目标元素。当到达目标元素时,激活冒泡模式,从目标元素传回到顶部元素。

可以向addEventListener传递参数,很容易地选择希望的事件处理顺序。第3个参数如果传入true,将采用事件捕获;如果传入false,则采用事件冒泡。因此,某种意义上来说,W3C标准更倾向于优先选择事件冒泡,默认是事件冒泡

首先从上到下进行捕获,如果遇到捕获模式的元素就执行事件。接着向下捕获直到底部元素,然后转为冒泡模式,从底部到顶部依次处理

this关键字指向的是事件处理器注册的元素,不一定是发生事件的元素。

通过事件代理,我们必须确保代理的元素是目标元素的祖先元素。这样,我们可以确定单击事件最终会冒泡到事件代理注册的元素上。

比如,我们要为一个被激活的表格添加样式,可以创建唯一的处理器,注册到比单元格更高层级的元素上,通过冒泡可以处理所有的单元格单击事件。我们知道单元格是表格的后代元素,通过event.target即可获得被单击的元素。this指向注册的祖先元素

通过内置的CustomEvent构造函数和dispatchEvent方法,创建和分发自定义事件,减少应用程序不同部分之间的耦合。

如加载动画时实现: