事件和回调函数
任务队列其实是事件的一个队列,也可以理解为消息队列,当IO设备完成一个任务的时候,就会在任务队列中添加一个事件,用来表示当前任务已经执行完了,可以进入执行栈(也就是之前讲过的主线程队列)了,主线程读取任务队列也就是读取有哪些事件!
任务队列中的事件除了IO设备之外,还有用户点击、键盘事件等,只要指定过回调函数,这些事件发生时就会进入任务队列,然后等待主线程读取。
回调函数(callback)其实就是被主线程挂起来的代码,主线程执行异步任务,其实就是执行回调函数!
任务队列其实是一个先进先出的数据结构,也就是说排在前面的事件会被优先读取,当主线程的代码执行完,执行栈被清空以后,就会立即执行任务队列中排在最前面的事件,但是由于定时器的功能,因此,主线程需要检查一下执行时间,某些事件只有等到规定的时间,才可以返回主线程!
Event Loop
主线程从任务队列读取事件这个过程是循环不断的,因此整个过程又被称为Event Loop(事件循环)
setTimeout(() => {
console.log('timeout1');
}, 0)
console.log(1);
setTimeout(() => {
console.log('timeout2');
}, 0)
console.log(2);
// 1 2 timeout1 timeout2
上面代码中有两个定时器,定时器也会放在任务队列中,因此我们常说在js中定时器可以模拟异步,其实是js默认会把定时器放在任务队列,前面我们讲过,js先会执行主线程的代码,称为执行栈,当执行栈的代码执行结束,执行栈清空之后才会执行任务队列中的代码, 因此上面的代码不会因为定时器在前面,而先执行定时器,当定时器的间隔时间一致时,按照添加顺序,先进任务队列则先执行!
NodeJs的Event Loop
NodeJs也是单线程的Event Loop,但是它区别于浏览器的运行环境;
在Nodejs中提供了process.nextTice()和setImmediate()两个与任务队列有关的方法;
process.nextTice()就是在当前执行栈尾部添加任务,也就是任务队列(所有的异步任务)开始之前;
console.log(1);
setTimeout(function timeout() {
console.log('timeout1');
}, 0);
process.nextTick(function() {
console.log(3);
process.nextTick(function(){
console.log(4);
});
});
setTimeout(function timeout() {
console.log('timeout2');
}, 0);
console.log(2);
// 1 2 3 4 timeout1 timeout2
setImmediate()会在每一次Event Loop结束执行,或者说下一次Event Loop执行之前执行
console.log('start');
setTimeout(function() {
console.log('timeout1');
}, 0);
setImmediate(function (){
setImmediate(function() {
console.log(1);
setTimeout(function() {
console.log('timeout3');
}, 0);
setImmediate(function(){
console.log(2);
});
});
setTimeout(function() {
console.log('timeout2');
}, 0);
});
process.nextTick(function() {
console.log('nextTick1');
process.nextTick(function() {
console.log('nextTick2')
})
})
console.log('end');
// start end nextTick1 nextTick2 timeout1 1 timeout2 2 timeout3
上面代码前三个输出结果在没有其他干扰,就目前代码,不用质疑,主线程肯定先执行,接下来为nextTick,因为它会被放在所有异步执行之前,不论是否嵌套(不包括嵌套在其他异步函数中),timeout1在主线程程中被添加到任务队列,不论它是否在start之后还是end之前,它是区别于其他函数的唯一一个在任务队列顶端的函数,而setImmediate总会在一个Event Loop之后执行!