我不该动你的,Event Loops
写在前面的话
本意是想好好研究下 Event Loops, 看了几篇博客后,才意识到作为前端打字员的我有多无知,这坑忒深了。
macrotask?,microtask?,MutationObserver? 这些都是啥?规范还没写清楚?不同浏览器运行的还未必一样?
但为了使自己养成经常总结的习惯,还是写点什么吧。
故事的开始
计算 fib(45)
是一个相当耗时的工作,在我的chrome里约需要15s左右。
问,页面什么时候会变成红色?在执行 console.now(1)
之前就变成红色了吗?
可以看到即使在 console.now(1)
执行之后,页面仍旧没有变红。
关于这个现象,可以有两种解释:
-
document.getElementsByTagName('body')[0].style.backgroundColor = 'red'
被当作一个异步事件,作为一个 task,被添加到 event loops - 渲染引擎要等到 JS 引擎空闲时才开始工作
到底是哪一种?所以将上述代码修改下
又增加了一个 setTimeout
。这样的话,如果是第一种解释,应该在 console.now(3)
运行之前,页面就变成了红色;否则就应该采取第二种解释。
运行结果如下,
可以看到在 console.now(3)
之后,页面依旧没有变色,看来就是渲染引擎要等到JS引擎完全空闲时才工作。
事情就这样结束了吗
没有,直到我看到
An must continually run through the following steps for as long as it exists:
- Let oldestTask be the oldest on one of the 's , if any, ignoring, in the case of a , tasks whose associated
Document
s are not . The user agent may pick any . If there is no task to select, then jump to the microtasks step below.- Set the 's to oldestTask.
- Run oldestTask.
- Set the 's back to null.
- Remove oldestTask from its .
- Microtasks: .
- Update the rendering: If this is a (as opposed to a ), then run the following substeps.
…...
这段话第7点的意思,怎么理解起来像是每执行一次 Event Loops 的 task,最后都会更新视图。
后来看到中
渲染更新(Update the rendering)会在event loop中的tasks和microtasks完成后进行,但并不是每轮event loop都会更新渲染,这取决于是否修改了dom和浏览器觉得是否有必要在此时立即将新状态呈现给用户。
会不会两次 setTimeout 被合并了?
这样调整之后,在运行 console.now(3)
之前,页面的颜色就变了
这样看来,就是在每一次task之后就可能会更新视图,而不是等到JS引擎空闲
在执行完setTimeout0
后,Event Loops 中实际上仍有 setTimeout1
待执行,但是浏览器先渲染了视图,再执行了setTimeout
,这就推翻了之前渲染引擎要等到 JS 引擎空闲(Event Loops为空)时才开始工作。
同时我怀疑,之前代码
setTimeout(function () { console.now(0) document.getElementsByTagName('body')[0].style.backgroundColor = 'red' console.now(1) fib(45) console.now(2) }, 1000) setTimeout(function () { console.now(3) fib(45) console.now(4) }, 1000)
会不会被优化成
setTimeout(function () { console.now(0) document.getElementsByTagName('body')[0].style.backgroundColor = 'red' console.now(1) fib(45) console.now(2) console.now(3) fib(45) console.now(4) }, 1000)