Event Loop是什么
- Event Loop是浏览器的一种运行机制,采用这种机制,来解决JavaScript单线程运行带来的问题
JavaScript单线程
- 进程:运行以后的程序叫做"进程"。一个进程一次只能执行一个任务。
- 线程:它被包含在"进程"之中,是"进程"中的实际运作单位,一个进程中可以并发多个线程,每条线程并行执行不同的任务
- JavaScript从诞生起就是单线程。原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,JavaScript为一种单线程语言
- JavaScript代码是在浏览器的渲染进程(Renderer Process)里的JS引擎线程(单线程)执行的
- 所有的 JS 代码其实都由 renderer Process 控制的,所以在你浏览网页内容的过程大部分时候不会涉及到其它的进程
没有Event Loop机制
- 单线程的弊端在于,一旦前面遇到大量任务或者遇到一个耗时的任务,网页就会出现"假死",因为JavaScript停不下来,也就无法响应用户的行为,
- 在实现交互时,无法避免,当某个任务很耗时,比如涉及很多I/O(输入/输出)操作,此时线程运行如下:
这样将闲置资源,显然是不合理的。Event Loop就是为了解决这个问题而提出的
有Event Loop机制
- 需要注意的是,Event Loop机制不是一直存在在线程中,是当主线程的所有同步任务执行完毕后,系统就会读取"任务队列",读取任务栈里面的异步任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。重复以上操作
- 主线程重复寻找任务队列的异步任务,可以用一张图说明一下流程
- 那么Event Loop机制是如何处理主线程和其他线程的通信,达到资源充分利用的呢? 首先我们先来认识一下Event Loop中重要的概念:任务队列,同步任务和异步任务
任务队列
JavaScript中,有两类任务队列:宏任务队列(macrotask ),微任务队列(microtask )。宏任务队列可有多个,微任务只有一个。
- 宏任务:script(主代码块),setTimeout,setInterval,I/O,UIrendering
- 微任务:Promise,Object.oberver,process.nextTick(node.js进程中的相关对象),MutationObserver
同步任务和异步任务
JavaScript单线程中,任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构
- 同步任务:可以立即执行的任务,例如声明一个变量或者执行一次加法操作等。同步任务属于宏任务
- 异步任务:是不会立即执行的事件任务。异步任务包括宏任务和微任务
现在我们可以看一段代码理清一下
console.log("script start")
setTimeout(function(){
console.log('timeout1')
},10);
new Promise(resolve=>{
console.log("promise1");
resolve();
setTimeout(()=>{
console.log('timeout2')
},10);
}).then(function(){
console.log("then1")
})
console.log("script end")
首先,先将同步任务执行,即输出script start,遇到异步任务setTimeout1,放入任务队列中,继续往下,执行同步任务,输出promise1, resolve()为异步任务,放入任务队列,往下遇到setTimeout2,放入任务队列中,往下遇到同步任务,输出script end。先同步任务全部执行完毕,去任务队列,先查看有无可执行的微任务,有pormise,故将该任务放入主线程中执行,输出then1,无可执行的微任务,查看有无可执行的宏任务,有setTimeout1,故输出timeout1,有无可执行的微任务,无则去查看有无可执行的宏任务,有setTimeout2,故输出timeout2 执行结果如下:
script start
promise1
script end
then1
timeout1
timeout2
总结
事件循环机制是发生在浏览器渲染进程中js引擎线程(主线程)和事件触发线程的之间的,然后任务队列是放在事件触发线程,然后调用栈是放在主线程。js引擎先执行完调用栈的同步任务,异步任务放到任务队列,调用栈执行完毕,去任务队列寻找可以调用的任务(先调用微任务,在调用宏任务),放到调用栈中,执行这个异步任务在js引擎的回调函数。(粗体为Event Lop事件循环),我画了如下图总结这段话