eventloop 又叫事件循环,指的是浏览器或者node在解决javascript单线程运行不会阻塞的一种机制。 在解释浏览器的事件循环之前先解释一下浏览器的运行机制。
一、浏览器多进程
首先,浏览器是多进程的,分为:
1.主进程(Browser 进程)
负责主控和协调、浏览器界面显示,与用户交互。如前进,后退等、负责各个页面的管理,创建和销毁其他进程、网络资源的管理,下载等;
2.第三方插件进程
每种类型的插件对应一个进程,仅当使用该插件时才创建;
3.GPU进程
只有一个,用于3D绘制,页面使用硬件加速才会用到他(比如看高清视频flash,显卡硬件加速)
4.浏览器渲染进程(浏览器内核)
也叫renderer进程,负责脚本执行、事件触发、队列的轮询等。打开多个浏览器,每一个tab浏览器都会启动一个渲染进程,内部是多线程的。
二、浏览器renderer进程是多线程的
1.JS引擎线程
也叫js内核,负责处理js脚本程序。一个渲染进程里只会有一个js线程解析js脚本,运行代码。(注意:GUI 渲染线程与 JS 引擎线程是互斥的,所以如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞)
2.GUI 渲染线程
负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制。这里就涉及到回流和重绘,当页面需要重绘或者某些操作引起回流的时候,就会执行这个线程。当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。为什么会和js引擎线程互斥呢? 简单来说,因为js是可以操作dom的 如果你在执行js线程的同时执行UI线程,那就有可能导致渲染线程前后获得的元素不一样(或者说是js修改dom后没有重新渲染)
3.事件触发线程
对应事件触发时,该线程会将事件对应的回调函数放入callback中,等待js引擎线程处理
4.定时触发器线程
对应于setTimeout,setInterval API (由于js单线程,会阻塞,怕影响计时准确,通过单独线程来计时并触发定时) 计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行
5.异步 http 请求线程
在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 JavaScript 引擎执行
三、事件循环 执行顺序
Javascript单线程任务被分为分为两种 同步任务 异步任务
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕 才能执行后一个任务
异步任务:不进入主线程 而进入任务队列的任务 只有任务队列通知主线程 某个任务队列可以执行 该任务才会进入主线程。异步任务必须指定回调函数 主线程执行异步任务 就是执行对应的回调函数)
在JavaScript中,任务被分为两种,一种宏任务 另外一种叫微任务
宏任务:script全部代码、setTimeout、setInterval、setImmediate等
微任务:Process.nextTick(Node)、Promise、MutationObserver
接下来看一个比较简单的事件循环的题目
console.log("script start");
async function async1() {
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2 end");
}
async1();
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise((resolve) => {
console.log("Promise");
resolve();
})
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
自上而下执行的顺序 async/await 在底层转换成了 promise 和 then 回调函数。 也就是说,这是 promise 的语法糖
首先,打印script start,调用async1()时,返回一个Promise,所以打印出来async2 end。
每个 await,会新产生一个promise,但这个过程本身是异步的,所以该await后面不会立即调用。
继续执行同步代码,打印Promise和script end,将then函数放入微任务队列中等待执行。
同步执行完成之后,检查微任务队列是否为null,然后按照先入先出规则,依次执行。
然后先执行打印promise1,此时then的回调函数返回undefinde,此时又有then的链式调用,又放入微任务队列中,再次打印promise2。
再回到await的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,打印async1 end。
当微任务队列为空时,执行宏任务,打印setTimeout。
所以最后的输出就是:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout