浅谈事件循环机制

216 阅读4分钟

浏览器内核

浏览器进程: 主进程,主要负责页面管理以及管理其他进程的创建和销毁等,常驻的线程有:

  • GUI渲染线程
  • JS引擎线程
  • 事件触发线程
  • 定时器触发线程
  • HTTP请求线程
GUI渲染线程
  • 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,JS引擎才会去执行GUI渲染。
JS引擎线程
  • 该线程当然是主要负责处理 JavaScript脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行 -该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。
事件触发线程
  • 主要负责将准备好的事件交给 JS引擎线程执行。
  • 比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。
定时器触发线程
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。
HTTP请求线程
  • 负责执行异步请求一类的函数的线程,如: Promise,anxios,ajax等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。

Render 进程 浏览器渲染进程(浏览器内核),主要负责页面的渲染、JS执行以及事件的循环。

Event Loop 是什么???

JavaScript的异步分为两种,宏任务(macro-task)和微任务(micro-task)

  • 宏任务:包括整体代码script,setTimeout,setInterval
  • 微任务:Promise.then(非new Promise),process.nextTick(node中)

执行机制:

  • 从全局任务 script开始,任务依次进入栈中,同步任务被主线程执行,执行完后出栈。
  • 遇到异步任务,交给异步处理模块处理,注册回调函数并放入事件队列里
  • 等待调用栈为空时,事件队列里的回调函数依次进入栈中执行。

当异步任务进入栈执行时,是宏任务还是微任务呢?

  • 由于执行代码入口都是全局任务 script,而全局任务属于宏任务,所以当栈为空,同步任务任务执行完毕时,会先执行微任务队列里的任务。
  • 微任务队列里的任务全部执行完毕后,会读取宏任务队列中排最前的任务。
  • 执行宏任务的过程中,遇到微任务,依次加入微任务队列。
  • 栈空后,再次读取微任务队列里的任务,依次类推。

练手DEMO

setTimeout(function() {
    console.log('setTimeout');
},1000)

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

1.setTimeout是个宏任务,放入宏任务队列中 2.new Promise 同步代码,立即执行console.log('promise'),然后看到微任务then,因此将其放入微任务的任务队列中 3.执行同步代码console.log('console') 4.主线程的宏任务,已经执行完毕,接下来要执行微任务,因此会执行Promise.then,到此,第一轮事件循环执行完毕 5.第二轮事件循环开始,先执行宏任务,即setTimeout的回调函数,然后查找是否有微任务,没有,时间循环结束

到此做个总结,事件循环,先执行宏任务,其中同步任务立即执行,异步任务,加载到对应的的Event Queue中(setTimeout等加入宏任务的Event Queue,Promise.then加入微任务的Event Queue),所有同步宏任务执行完毕后,如果发现微任务的Event Queue中有未执行的任务,会先执行其中的任务,这样算是完成了一次事件循环。接下来查看宏任务的Event Queue中是否有未执行的任务,有的话,就开始第二轮事件循环,依此类推。