栗子1
- 求下面函数的输出
console.log('script start');setTimeout(() => {console.log('setTimeoout');
}, 0);Promise.resolve().then(function(){console.log('promise1');
}).then(function(){console.log('promise2');
})
console.log('script end');
- 说明: 在"promise2"和"setTimeoout"之间有"<· undefined"
- “<· undefined”: 其实是进入了下一轮事件循环
- 可视化展示: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
JS的执行顺序
- 原则:
- 事件循环过程中,每次只执行1个宏任务
- 在执行宏任务之前,首先检查微任务队列是否为空.若存在微任务,则先执行微任务
常见的宏任务和微任务
- 常见的宏任务: setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame(浏览器)、I/O、UI rendering(浏览器)
- 常见的微任务: process.nextTick(Node)、promise.then()、Obeject.observe、MutationObeserve
栗子1说明:
- 在了解了执行顺序以及宏/微任务之后,再看上面的栗子1:
console.log('script start')
: 同步任务, 输出 ‘script start’setTimeout(function(){ console.log('setTimeout') },0)
: 这是一个宏任务,会将函数function(){ console.log('setTimeout') }
推到宏任务队列中Promise.resovle().then(function(){console.log('promise1')}).then(function(){console.log('promise2')})
: 2个微任务,一次推入微任务队列console.log('script end')
: 同步任务,输出 ‘script end’- 到了这里,开始执行事件循环的下一轮,根据原则2.先检擦微任务队列是否为空,此时不为空,于是执行队列的第一个(即输出 ‘promise1’),然后出队,在检查微任务队列是否为空(此处不为空,故输出’promise2’,出队),在检查…
- 当微任务队列为空,代表当前事件循环结束,所以会输出一个返回值,此处未设定,故输出"<· undefined"
- 从宏任务队列中读取,输出(“setTimeout”)
栗子2
- 求以下函数的输出结果
new Promise(resolve => {console.log("resolve")resolve()}).then(() => console.log("promise then..."))setImmediate(() => {console.log("set immediate...")
})setTimeout(() => {console.log("set Timeout ...");
}, 0);process.nextTick(() => {console.log("nextTick")
})
- 说明:
- Promise是宏任务,其里面的函数是同步的.即会执行
console.log('resolve')
- nextTick可以理解为在其他类型微任务的前面入队.
浏览器中的事件循环
- 执行全局Script的同步代码
- 执行microtask任务
- 从宏任务队列中取出队首一个任务
- 执行该任务
- 任务执行完毕,检查是否有微任务(有则执行,否则执行第一步)
Node.js的Event Loop过程:
- 执行全局Script的同步代码
- 执行microtask微任务,先执行所有 Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
- 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,六个阶段: Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue…
- MacroTask包括: setTimeout、setInterval、setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering(浏览器)
- MicroTask包括:s process.nextTick(Node)、Promise.then、Object.observe、MutationObserver
setTimeout 和 setImmediate
-
setImmediate():方法用于中断长时间运行的操作,并在完成其他操作后立即运行回调函数
-
栗子:
setTimeout(() => {console.log('setTimeout');
}, 0);setImmediate(() => {console.log('setImmediate');
})
同样的代码执行的结果不确定:
- setTimeout/setInterval的第二个参数取值范围是: [1, 2^31 -1],如果超过这个范围就会初始化为1,即 setTimeout(fn, 0) === setTimeout(fn, 1);
- setTimeout的回调函数再timer阶段执行,setImmediate的回调函数再check阶段执行,event loop的开始会检查timer阶段,但是再开始之前到timer阶段会消耗一定时间,就会出现以下情况:
- timer前的准备时间超过1ms, 满足loop -> time >=1, 则执行timer阶段(setTimeout)的回调函数
- timer前的准备时间小于1ms,则先执行check阶段(setImmediate)的回调函数,下一次event loop执行timer阶段(setTimeout)的回调函数
栗子3
console.time("start");
setImmediate(function() {console.log(1);
});
setTimeout(function() {console.log(2);
}, 10);
new Promise(function(resolve) {console.log(3);resolve();console.log(4);
}).then(function() {console.log(5);console.timeEnd("start")
});
console.log(6);
process.nextTick(function() {console.log(7);
});
console.log(8);
- 说明:
- 首先执行script代码,输出3468
- 执行nextTick任务,输出7
- 执行microtask, 输出5, start: 15.232ms
- 如果事件大于10ms,则执行宏任务 “输出2”, 否则输出"1"