js 事件循环中的job queue和message queue

本文讨论的事件循环均是基于浏览器环境上的,相似nodejs环境下的事件循环与此并不相同。javascript

读者首先要对js单线程事件循环机制以及Promise有基本理解;若是这两个概念不是很清楚,建议先阅读下面两篇文章:java

THE JAVASCRIPT EVENT LOOP ; Promise 对象node

本文是基于THE JAVASCRIPT EVENT LOOP ,并对其内容的延伸,因此下面提到的概念都按这篇文章的来。首先我会总结一下 THE JAVASCRIPT EVENT LOOP 。OK,让咱们开始吧。es6

1,消息队列(message queue)

      咱们知道js单线程的实现方式会把异步任务(setTimeout回调函数,事件监听回调函数等)放在一个消息队列中;当主任务队列任务为空时会去message queue查询是否有等待执行的任务,若是有则执行。chrome

 例1:promise

var task_in_message_queue = () => {console.log('task in message queue')}
setTimeout(task_in_message_queue,0);
console.log('main task');

//result:
//main task
//task in message queue

setTimeout函数将task_in_message_queue函数添加到message queue队列中。等到主任务队列执行完成时(此时已打印main task),执行存在message queue队列中的task_in_message_queue函数浏览器

2,任务队列(job queue)

        ES6中引入了任务队列来执行Promise的回调函数。同message queue同样,job queue中的任务也是在主任务队列为空时才开始执行。异步

例2:函数

var promise = new Promise((resolve,reject) => {
    resolve('task in job queue');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise.then(resolve_callback);
console.log('main task');

//result:
//main task
//task in job queue

/**
这里有一个有趣的现象

在chrome中打印出的结果是
main task
task in job queue
undefined  //主任务的函数返回值

在firefox中的结果是
main task 
undefined  //主任务的函数返回值
task in job queue

感受v8的实现是把job queue整合到了主任务队列尾部
**/

promise.then 将promise fulfilled状态下的回调函数resolve_callback添加到job queue中。等到主任务队列执行完成时(此时已打印main task),执行存在job queue队列中的resolve_callback函数oop

这里有一点须要注意的是promise构造函数会在主任务中当即执行,例子以下:

var promise = new Promise((resolve,reject) => {
    resolve('task in job queue');
    console.log('the promise construction executed');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise.then(resolve_callback);
console.log('main task');


//result:
//the promise construction executed
//main task
//task in job queue

3,任务队列(job queue)VS 消息队列(message queue)

       经过上面的例子咱们知道主任务队列优先级是最高的,那么job queue和message queue哪一个优先级更高呢?答案是job queue,js会将job queue中的任务彻底执行完以后再执行message queue中的任务。例子以下:

var message_task = () => {console.log('message task');}
setTimeout(message_task,0);
var promise1 = new Promise((resolve,reject) => {
    resolve('promise 1 resolved');
});
var promise2 = new Promise((resolve,reject) => {
    resolve('promise 2 resolved');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise1.then(resolve_callback);
promise2.then(resolve_callback);
console.log('main task');


//result:
//main task
//promise 1 resolved
//promise 2 resolved
//message task

/**
这里chrome和firefox返回undefined的位置同上面的例子同样,也是不一样的。有兴趣的话能够试试看一下。
**/

4,每次执行message queue中的任务前都会检查job queue吗?

        如今咱们知道job queue的优先级高于message queue。那么每次执行message queue中任务前会检查job queue吗?个人意思是若是当前job queue为空,message queue中有多个任务(假设有m_task1和m_task2)。js开始执行message queue中的任务,在执行完m_task1时插入了一个j_task1在job queue中。那么接下来是先执行m_task2呢仍是j_task1呢?若是先执行了m_task2的话,就说明js一旦开始执行message queue中的任务就会将全部message queue中任务执行完再检查其它任务队列。若是先执行j_task1的话,那么说明再执行每一个message queue中的任务前都会先检查其它任务队列,先执行优先级高的任务队列中的任务。为此咱们用以下代码来检验:

var promise_task = new Promise((resolve,reject) => {
    resolve('j_task1');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
var message_task1 = () => {
    promise_task.then(resolve_callback);
    console.log('m_task1');
}
var message_task2 = () => {console.log('m_task2');}
setTimeout(message_task1,0);
setTimeout(message_task2,0);


//result:
//m_task1
//j_task1
//m_task2

事实证实js在每次执行message queue中的任务前都会检查其它任务队列(至少会检查job queue),根据队列优先级决定先执行哪一个队列中的任务。

5,主任务队列呢?

        上面咱们了解了job queue和message queue中任务的执行顺序,简而言之:在每次一个任务结束时,js都会根据任务队列的优先级判断下一个执行任务是哪一个。若是job queue中有任务则执行job queue中的第一个任务,不然执行message queue中的第一个任务。那么主任务队列是否是也同样呢?(逻辑上应该是同样的,不然job queue或者message queue中的任务能够递归建立新任务,这样就永远没法回到主任务队列了)。

        即每次选择执行任务前(或者每次任务结束后),js会根据主任务队列,job queue,message queue的优先级来挑选将要执行下一个任务是哪一个。

        为此咱们声明一个promise和一个message_task函数。在这个promise的回调函数中使用setTimeout建立一个message_task的message queue任务,同时在message_task中调用promise.then 函数建立一个job queue 任务。这样两个任务会循环建立并循环执行。运行后咱们会在console中看到两个任务循环打印,这是咱们在console中键入alert('stop')命令。若是页面显示了alert,console中止了打印就说明主任务队列的行为方式和job queue,message queue是同样的。不然的话,在这种状况下咱们将永远没法回到主任务队列。验证代码以下:

var promise_task = new Promise((resolve,reject) => {
    resolve('j_task');
});
var resolve_callback = (resolve_message) => {
    setTimeout(message_task,0);
    console.log(resolve_message);
}
var message_task = () => {
    promise_task.then(resolve_callback);
    console.log('m_task');
}

promise_task.then(resolve_callback);


//result:
//console会循环打印 j_task 和 m_task
//这是在console中键入alert('stop')命令,观察是否弹出alert框,console中打印是否终止

但愿你们自行求证一下,固然验证完毕后记得刷新页面,否则可能就崩了。另:最好在chrome下验证,firefox有些卡顿。

总结

        js事件循环规律可大体总结为以下:

        1,js中有三个任务队列:主任务队列,job queue,message queue;

        2,它们的优先级是:主任务队列 > job queue > message queue;

        3,每当要执行下一个任务前(或者一个任务完成后),js会根据优先级询问各个任务队列是否为空,一旦遇到非空任务队列时则取其第一个任务执行。

相关文章
相关标签/搜索