这里先来看一段代码,这是HTML规范里提到的:
eventLoop = { taskQueues: { events: [], // UI events from native GUI framework parser: [], // HTML parser callbacks: [], // setTimeout, requestIdleTask resources: [], // image loading domManipulation: [] }, microtaskQueue: [ ], nextTask: function() { // Spec says: // "Select the oldest task on one of the event loop's task queues" // Which gives browser implementers lots of freedom // Queues can have different priorities, etc. for (let q of taskQueues) if (q.length > 0) return q.shift(); return null; }, executeMicrotasks: function() { if (scriptExecuting) return; let microtasks = this.microtaskQueue; this.microtaskQueue = []; for (let t of microtasks) t.execute(); }, needsRendering: function() { return vSyncTime() && (needsDomRerender() || hasEventLoopEventsToDispatch()); }, render: function() { dispatchPendingUIEvents(); resizeSteps(); scrollSteps(); mediaQuerySteps(); cssAnimationSteps(); fullscreenRenderingSteps(); animationFrameCallbackSteps(); intersectionObserverSteps(); while (resizeObserverSteps()) { updateStyle(); updateLayout(); } paint(); } } while(true) { task = eventLoop.nextTask(); if (task) { task.execute(); } eventLoop.executeMicrotasks(); if (eventLoop.needsRendering()) eventLoop.render(); }
事件循环并没有多说关于什么时候dispatch event:
1,每一个queue(队列)中的事件都是按顺序执行;
2,事件可以直接dispatch,绕过task queues(任务队列)
2,微任务是在一个task执行完成后立即执行;
3,渲染部分循环是在vSync上执行,并且按以下顺序传递事件:
  • 分派待处理的UI事件
  • resize事件
  • scroll滚动事件
  • mediaquery监听者(css @media)
  • CSSAnimation事件
  • Observers
  • requestAnimationFrame
 
下面来详细说一下
UI events有两类:
1,Discrete,俗称离散事件,就是那些不连续的,比如(mousedown,mouseup,touchstart,touchend)等
2,Continuous,俗称连续事件,就是连续的,比如(mousemove,mousewheel,touchmove,wheel)等
两种事件的注意点不同:
1,连续事件:一个UI event task queue中,相匹配的连续事件(比如持续更新position属性,或者持续改变大小),可能会合并,不管那些合并的事件是否被dispatch了,因为有的还没有被dispatch,或者排在了队列的后面。
2,离散事件:如果从硬件接收到了离散事件,就必须尽快dispatch,如果此时队列中有连续事件,就必须立即运行所有的连续事件,以防止离散事件的延迟。也就是说,触发离散事件的时候,连续事件必定已经全部dispatch完毕。
不同的浏览器对事件循环的顺序是不同的,我还是习惯以谷歌为准,因为谷歌浏览器是最接近规范的
下面列举一些谷歌dispatch event是通过哪些方法来dispatch的:
1,DOMWindowEventQueue
由计时器触发
示例事件:
window.storage window.hashchange document.selectionchange
2,ScriptedAnimationController
由Frame调用的BeginMainFrame函数触发,同时还管理requestAnimationFrame请求
示例事件:
Animation.finish Animation.cancel CSSAnimation.animationstart CSSAnimation.animationiteration(CSSAnimation)
3,Custom dispatch
触发器不同:OS events操作系统事件,timers定时器,文档/元素生命周期事件
Custom dispatch event 不通过队列,他们直接被触发(这里我猜想可能就是‘任务队列’的隐藏概念,他们不会排在普通队列中,而是单独去了另一个特殊的队列执行,这里就是‘任务队列’)
4,Microtask队列
微任务通常由EndOfTaskRunner.didProcessTask()触发,任务由TaskQueueManager运行,每当task完成时,微任务队列就会执行
示例事件:
image.onerror image.onload
微任务也包括Promise callbacks,这里注意,Promise的回调才是真正的微任务,之前说的可能不严谨,执行promise的时候本身还是正常task
5,主线程事件队列
连续事件会被合并处理。
关于Timer有几个方法:
requestIdleCallback
这个方法只有浏览器空闲的时候才会有内部的timer触发
requestAnimationFrame
由ScriptedAnimationController触发,这个方法挺重要的,主要是用来解决setTimeout和setInterval无法完成的动画效果。
Timers:setTimeout,setInterval
由运行在TaskQueue primitive上的WebTaskRunner触发的。
Observers
观察者分为两种,MutationObserver和IntersectionObserver。
MutationObserver:属于微任务,主要是负责监听DOM节点内的变化,前一篇随笔里有提到;
IntersectionObserver:属于轮询,可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。具体用法不多介绍,不过它可以用来实现懒加载、无限滚动,监听元素是否在视窗中,或者已经出现了多少内容。
Promises
执行完成后,回调会放到微任务队列中去。
 
以上就是HTML规范中提到的event loop事件模型的详解,欢迎感兴趣的朋友留言参与讨论!
文字免费,但码字不易,记得点赞!
感兴趣可关注一波,谢谢!
posted on 2025-05-29 14:48  言先生  阅读(9)  评论(0)    收藏  举报