Javascript 异步编程的演进
一、Javascript 异步的由来
Javascript 单线程
大家都知道 js 是单线程的,那为什么要是单线程的呢?
因为 js 的运用场景是浏览器,包含了很多用户的交互,如果是多线程,那一个线程要在某个 DOM 上添加内容,另一个线程直接要删除这个 DOM,那浏览器到底听哪个的好呢?所以为了降低复杂性,js 从一诞生,就是单线程,这也是这门语言的核心特征,因为 js 一开始就是为浏览器而生的
既然是单线程,也就是每次只执行一个任务,只有等到当前任务执行完毕,才能执行后面的任务,这些任务会形成一个任务队列,排队等候执行
就像大家去超市买东西排队结账,得前面一个人付完钱,排在他后面的那个才能买单。但是如果前面一个任务很耗时,比如正常每个人手里都是拿着一两样东西等着排队,而你前面那位大哥推着满满一车的东西,你是不是得崩溃了?
所以像我们平时遇到的浏览器无响应和页面假死,往往是因为某段 js 代码执行时间过长,或者直接陷入死循环,导致页面卡死,后面的任务当然就无法继续执行了
但是,在前端的某些任务的确是非常耗时的,比如网络请求、定时器和事件监听等等,如果让他们和别的任务一样都老老实实的排队等待执行的话,执行效率会非常低。所以,这时候浏览器为这些耗时的任务开辟了另外的线程,主要包括事件触发线程、定时器触发线程和异步 HTTP 请求线程
浏览器多线程
浏览器渲染进程是多线程的,它包含如下线程:
- GUI 渲染线程
- JS 引擎线程
- 事件触发线程
- 定时器触发线程
- 异步 HTTP 请求线程
1、GUI 渲染线程
负责渲染浏览器界面,解析 HTML、CSS
当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行
GUI 渲染线程与 JS 引擎线程是互斥的,因为 JS 可以操作 DOM 元素, 从而影响到 GUI 的渲染结果,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行
2、JS 引擎线程
JS 内核(例如V8引擎),负责处理 Javascript 脚本程序
JS 引擎一直等待着任务队列中任务的到来,然后加以处理
因为 GUI 渲染线程与JS引擎线程是互斥的,所以如果 JS 执行时间过长,页面渲染就不连贯,造成页面渲染加载阻塞
3、事件触发线程
由于 JS 引擎这个单线程的家伙自己都忙不过来,所以需要浏览器另开一个线程协助它
待处理队列中的事件都得排队等待 JS 引擎处理(当 JS 引擎空闲时才会去执行)
4、定时触发器线程
setInterval 与 setTimeout所在线程
JS 引擎阻塞状态下计时不准确,所以由浏览器另开线程单独计时
计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行
5、异步 HTTP 请求线程
如果请求有回调事件,异步线程就产生状态变更事件,将这个回调再放入事件队列中,等 JS 引擎空闲后执行