众所周知,javascript是一门单线程语言,而当咱们使用ajax和服务端进行通讯的时候是须要必定时间的,这样当前线程就会被阻塞,使浏览器失去相应。所以,当js执行执行一些长时间的任务时,咱们但愿有一种异步的方式处理这种任务。事件循环(event loop)就是如何处理异步执行顺序的一种机制。javascript
$.get(url, function (data) {
//do something
});
复制代码
接下来会一一介绍,事件循环中的执行栈
、事件队列
、宏任务
、微任务
等概念java
执行栈就是js代码运行的地方,上图call stack所示。当下面程序运行时,会推送的调用栈中被执行。node
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 500);
console.log('Bye');
复制代码
当浏览器中的事件监听函数被触发(DOM)、网络请求的相应(ajax)、定时器被触发(setTimeout)相对应的回调函数就会被推送到事件队列中,等待执行;如上图中的Callback Queue。ajax
事件循环是一个这样的过程:当执行栈中的任务结束以后,会将事件队列中的第一个任务推入到执行栈中执行,当任务处理完毕,又会取事件队列中的第一个任务,如此往复,便构成了事件循环。promise
对应到下面代码中。浏览器
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 500);
console.log('Bye');
复制代码
经过上面的例子会对执行栈和事件队列有个基本的认识。因为JS是单线程的,同步任务会形成浏览器阻塞,咱们把任务分红一个一个的异步任务,经过事件循环来执行事件队列中的任务。这就使得当咱们挂起某一个任务的时候能够去作一些其余的事情,而不须要等待这个任务执行完毕。因此事件循环的运行机制大体分为如下步骤:bash
一、检查事件队列是否为空,若是为空,则继续检查;如不为空,则执行 2;网络
二、取出事件队列的首部,压入执行栈;dom
三、执行任务;异步
四、检查执行栈,若是执行栈为空,则跳回第 1 步;如不为空,则继续检查;
咱们知道DOM操做会触发浏览器渲染,如增、删节点,改变背景颜色。那么这类操做是如何在浏览器当中奏效的?
至此咱们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,若是不为空,则取出队首压入执行栈执行。当一个任务执行完毕以后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的状况,这也使 JS 动画成为可能。
function move() {
setTimeout(() => {
dom.style.left = dom.offsetLeft + 10 + 'px'
move()
}, 15);
}
move()
复制代码
如今用事件循环的机制说明js动画的过程。上面代码会在执行栈中执行,move函数被调用,setTimeout的回调函数15ms以后会被推送到事件队列中。此时执行栈中的任务结束,浏览器渲染、检查事件队列不断循环。当15ms以后事件队列中有任务时,会被推送到执行栈中执行,这时dom节点向右偏移10px,move函数执行、执行栈结束,浏览渲染、检查事件队列。如此往复就造成了动画。
先看一段代码,是如何输出的;
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
});
console.log('script end');
复制代码
答案是:'script start'
、'script end'
、'promise1'
、'promise2'
、'setTimeout'
。
setTimeout的回调函数是宏任务、Promise的回调函数是微任务。微任务和宏任务同样遵循事件循环机制,可是他们仍是有些差异。
一、宏任务和微任务的事件队列是相互独立的;
二、微任务队列的检查时机早于宏任务。(执行栈中任务结束就会立刻清空微任务事件队列)
根据上面的规则,解释代码的输出。
执行栈中的代码执行,宏任务推入宏任务事件队列、微任务推入微任务事件队列,执行栈任务结束
检查微任务事件队列,此时已经有Promise的回调函数,推入执行栈,输出promise1
。Promise还有回调函数,推入微任务事件队列,执行栈结束。
检查微任务事件队列,推入执行栈,输出promise2
,执行栈结束。
检查微任务事件队列,此时被清空
检查宏任务事件队列,推入执行栈,输出setTimeout
,执行栈结束。
宏任务有: **setTimeout** 、**setImmediate** 、 **MessageChannel**
微任务有: **setTimeout** 、**setImmediate** 、 **MessageChannel**
复制代码
Node中的事件循环是和浏览器有很大区别的
当Node.js启动时,会初始化event loop;每一个event loop都会包含按以下顺序六个循环阶段
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────
复制代码
每个阶段都有一个装有callbacks的fifo queue(队列),当event loop运行到一个指定阶段时, node将执行该阶段的fifo queue(队列),当队列callback执行完或者执行callbacks数量超过该阶段的上限时,event loop会转入下一下阶段。
宏任务:setTimeout和setImmediate
复制代码
谁先输出,谁后输出?
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
复制代码
答案是不肯定的。有两个前提咱们是须要清楚的;
当:event loop准备时间 > setTimeout最小毫秒数。从timers阶段检查,此时队列中已经有setTimeout的任务,因此timeout
先输出;
当:event loop准备时间 < setTimeout最小毫秒数。从timers阶段检查,此时队列是空的就下检查接下来的阶段,到check阶段,已经有setImmediate的任务,因此immediate
先输出;
微任务:process.nextTick()和Promise.then()
复制代码
微任务不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行;nextTick比Promise.then()先执行
下面代码是如何执行的。
setImmediate(() => {
console.log('setImmediate1')
setTimeout(() => {
console.log('setTimeout1')
}, 0);
})
setTimeout(()=>{
process.nextTick(()=>console.log('nextTick'))
console.log('setTimeout2')
setImmediate(()=>{
console.log('setImmediate2')
})
},0);
复制代码
setImmediate1
,setTimeout的任务添加到timer阶段setTimeout2
,setImmediate的任务添加到check阶段。setTimeout1
nextTick
setImmediate2
let fs = require('fs')
fs.readFile('./1.txt', 'utf8', function (err, data) {
setTimeout(() => {
console.log('setTimeout')
}, 0);
setImmediate(() => {
console.log('setImmediate')
})
})
复制代码
这种状况下的setTimeout和setImmediate执行的顺序肯定吗?readFile的回调函数是在poll阶段执行 答案是setImmediate
比setTimeout
先执行
浏览器中和Node.js中的事件循环能够说是两套不一样的机制,作个总结,但愿有所帮助。