单线程、同步和异步
JS是单线程,单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。
此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,CPU串行执行任务(通过执行调用栈,先进后出);异步执行就是跳过等待,先处理后续的同步任务(不是说异步不执行了,而是交给网络模块、timer等并行进行任务)。因此产生了事件循环机制。
宏任务和微任务
现在说事件循环就必须先知道宏任务和微任务的概念。
- 宏任务
- 新程序或子程序被直接执行,比如
script、在控制台直接写代码。 - 事件回调函数,比如鼠标点击事件
setTimeout、setInterval- I/O操作、
requestAnimationFrame等。
- 微任务
Promise,常用的就是这个。MutationObserverNode.js的Process.nextTick()
事件循环
- 在执行调用栈的时候会先执行同步任务。
- 调用栈在发现异步任务时候会把异步任务放入队列中,异步任务分为宏任务和微任务,队列按照先入先出的规则。
- 事件循环就是会不断寻找可以执行的任务,同步任务执行完后也就是调用栈清空后,会去执行微任务队列。
- 微任务队列清空后才会去执行宏任务。
示例
console.log('sync')
Promise.resolve().then(function promise1 () {
console.log('promise1');
})
setTimeout(function setTimeout1 (){
console.log('setTimeout1')
Promise.resolve().then(function promise2 () {
console.log('promise2');
})
}, 0)
setTimeout(function setTimeout2 (){
console.log('setTimeout2')
}, 0)
// sync
// promise1
// setTimeout1
// promise2
// setTimeout2
事件循环过程:
-
第一轮
整个
script是一个宏任务,在调用栈中执行。script推入宏任务。sync为同步任务,promise1为微任务,setTimeout1列为宏任务,setTimeout2列为宏任务。执行完毕清空调用栈。微任务中有
promise1,执行完毕后浏览器可能进行渲染。script退出宏任务。事件循环开始寻找下一个可以执行的任务。
-
第二轮
取出
setTimeout1进入调用栈执行完毕,并把promise2推入微任务。promise2执行完毕,浏览器可能进行渲染。事件循环开始寻找下一个可以执行的任务。
-
第三轮
取出
setTimeout2进入调用栈执行完毕。