JS的单线程与浏览器的多线程
- JS的单线程:JavaScript代码的运行是单线程的,采用单线程模式工作的原因也很简单,最早就是在页面中实现Dom操作,如果采用多线程,就会造成复杂的线程同步问题,如果一个线程修改了某个元素,另一个线程又删除了这个元素,浏览器渲染就会出现问题。
- 浏览器的多线程
- 四大进程:主进程、第三方插件进程、GPU进程、渲染进程
- 渲染进程中的线程:JS引擎线程、事件触发线程、定时触发线程、GUI渲染线程
浏览器 JS 异步执行的原理
为什么单线程的JS可以执行异步任务呢?
因为浏览器是多线程的,当JS需要执行异步任务的时候,浏览器就会去启动一个线程去执行。
比如遇到异步任务setTimeout,此时浏览器启动定时触发线程去执行,当执行完毕后再将异步任务注册的回调放到任务队列中,等到JS引擎空闲的时候,取出来执行。 即浏览器才是真正执行异步任务setTimeout的角色,而 JS 只是负责执行最后的回调处理
浏览器中的事件循环
执行栈与任务队列
执行栈采用的是后进先出规则,函数执行的时候就会被压入栈的顶部,当执行完后,就会从栈顶弹出。JS在解析一段代码的时候就会将代码压入栈中执行,当遇到异步任务的时候就会交给其他的线程处理,待当前执行栈执行完所有同步代码后,会循环检测任务队列中有没有已完成的异步任务的回调,有则从任务队列中取出已完成的异步任务的回调加入执行栈继续执行,如此循环。这个过程就是事件循环,而每一次循环就是一个事件周期或称之为一次 tick。
宏任务和微任务
任务队列还分为宏任务队列和微任务队列,对应的里面用于存放宏任务和微任务,宏任务和微任务都属于异步任务。
事件循环过程中,执行栈执行完同步任务后优先检查微任务队列是否有需要执行的任务,如果没有再去宏任务队列中检查是否有需要执行的任务。微任务一般在当前循环就会优先执行,而宏任务会等到下一次循环,因此,微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。
- 宏任务
- 有明确的异步任务需要执行和回调;需要其他异步线程支持。
- 有明确的异步任务需要执行和回调;需要其他异步线程支持。
- 微任务
- 没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。
- 没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。
视图更新渲染
微任务队列执行完成后,也就是一次事件循环结束后,浏览器会执行视图渲染,当然这里会有浏览器的优化,可能会合并多次循环的结果做一次视图重绘,因此视图更新是在事件循环之后,所以并不是每一次操作 Dom 都一定会立马刷新视图。视图重绘之前会先执行 requestAnimationFrame 回调,那么对于 requestAnimationFrame 是微任务还是宏任务是有争议的,在这里看来,它应该既不属于微任务,也不属于宏任务。