问题
下面的代码在执行后会发生一闪之后消失的情况吗?
const div = document.createElement('div')
document.body.appendChild(div)
div.innerHTML = "test"
div.style.display = 'none'
setTimeout
The setTimeout(callback, ms) method, when invoked, must run the following steps:
-
Run the following steps in parallel:
-
Wait ms milliseconds
-
Queue a task to run the following steps:
- Invoke callback.
-
Task Queues
如果没事情干,事件循环就以 CPU 经济的方式一直空转。
如果有事情做,那么就将要做的事情放到任务队列里面等待调度。
下图右边的这个开关就是每秒打开 60 次。
while 死循环时任务就没有办法执行完成,所以渲染就没法执行了。
setTimeout 死循环的时候,就是一直将任务加到任务队列,所以渲染是可以执行的。
微任务的死循环也会阻止渲染。
所以上面的代码是不会有问题的,因为它一定是在所有的代码执行完成之后才开始的渲染。
requestAnimationFrame
会在渲染之前执行 raf 回调。Safari 和 Edge 是在 Paint 之后调用的。
使用 setTimeout
setTimeout(callback, 1000 / 60)
callback 有可能执行时间过长导致两次渲染间隔不一致。
使用 requestAnimationFrame,在回调之前执行
0-1000-500 的动画
实测这个代码并不能起作用
document.getElementById('button').addEventListener('click', (e) => {
const box = document.getElementById('box')
box.style.transform = 'translateX(500px)'
box.style.transition = 'transform 1s ease-in-out'
requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = 'translateX(200px)'
})
})
})
Micro Tasks
背景
90年代,想监听 DOM 的变动,给我们提供了 DOMNodeInserted 的事件。
下面这段代码会触发200次监听。这个时候如果监听里面任务哪怕执行的很短,也会有可能卡顿。能不能一次性添加完200个,然后再通知我们?
所以新建了一个微任务队列,可以在100次按钮添加完成之后再通知我们。
document.body.addEventListener('DOMNodeInserted', () => {
console.log('Stuff added to <body>!')
})
for (let i = 0; i < 100; i++) {
const span = document.createElement('span')
document.body.appendChild(span)
span.textContent = `Hello ${i}`
}
解决方案
使用 MutationObserver,这个应该就是 MutationObserver 诞生的背景。
const observer = new MutationObserver(() => {
console.log('Stuff added to <body>!')
})
observer.observe(document.body, {
childList: true
})
Promise
Promise 在回调执行时,浏览器会保证没有其他 JS 只执行到一半。这个就是为什么要采用微任务实现的原因。
任务执行
任务队列一次只处理一个。如果有另一个事件进来,就放到队尾。
动画回调会一次执行完,如果过程中又提交了新的,他们会延迟到下一帧。
微任务会执行到完成,包括插进来的,如果你的提交和处理速度一样快,会一直在处理微任务。
小测验
const button = document.getElementById('button')
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 1'))
console.log('Task 1')
})
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 2'))
console.log('Task 2')
})
button.click()
打印结果是什么?
第一次: Task 1 -> Task 2 -> Microtask 1 -> Microtask 2
后续点击: Task 1 -> Microtask 1 -> Task 2 -> Microtask 2