调度、idle 与优先级
调度、idle 与优先级
机制拆解
调度 API 只改变任务进入队列的方式,不会把同步函数自动变成后台线程。先用一个定义完整的任务比较支持路径与降级路径:
html
<output id="output">等待任务</output>
<script>
const output = document.querySelector('#output')
const task = () => { output.value = 'background task ran' }
if ('scheduler' in globalThis && 'postTask' in globalThis.scheduler) {
globalThis.scheduler.postTask(task, { priority: 'background' })
} else {
setTimeout(task, 0)
}
</script>记录按钮点击、任务执行和页面绘制的顺序;把 priority 改为 user-visible,只能比较队列优先级的观察结果,不能推断它改变了 CPU 执行时间。
边界与失败路径
- 所有权:监听器、observer、worker、连接、缓存和焦点都有创建者与释放时机;页面隐藏、路由离开和元素移除时都要验证。
- 宿主差异:SSR、Web Worker、Shadow DOM、跨文档、跨源和辅助技术不会共享同一套全局对象与事件路径。
- 错误分类:取消、超时、权限拒绝、协议错误、配额不足和浏览器不支持应分别进入可观察状态。
- 安全边界:用户输入、URL、HTML、消息和持久化数据不能仅因为来自同源或类型正确就视为可信。
可运行实验
把下面页面另存为 scheduler-lab.html,点击按钮 30 次。记录控制台中 scheduled 与 ran 的顺序;再把 scheduler.postTask 分支临时改为 setTimeout,比较顺序是否稳定。
html
<button id="run">排队任务</button><output id="output"></output>
<script>
const output = document.querySelector('#output')
const run = () => {
output.value += ' ran'
console.log('ran')
}
document.querySelector('#run').addEventListener('click', () => {
output.value += ' scheduled'
console.log('scheduled')
if ('scheduler' in globalThis && 'postTask' in globalThis.scheduler) globalThis.scheduler.postTask(run, { priority: 'background' })
else setTimeout(run, 0)
})
</script>实验记录要写明浏览器是否支持 scheduler.postTask;只比较任务进入回调的顺序,不把一次观察推导成所有设备上的性能结论。
练习
- 解释一次点击中
scheduled先于ran的原因,并说明setTimeout降级保留了什么契约。 - 连续点击后刷新页面,说明页面卸载时尚未执行的任务是否还能更新 DOM,以及如何取消自己创建的任务。
- 将优先级改为
user-visible,写出仍然不能由该实验证明的性能结论。
下一步
完成练习后回到本章概览,按下一篇继续;遇到跨章节故障,先回到本节的所有权与宿主边界。