核心有三个:同步事件消息队列,异步事件消息队列,事件循环系统。(后文简称同步队列、延迟队列、事件循环)

    浏览器中有一个同步事件消息队列,
    所有运行在主线程上的任务,都需要先添加到同步队列,然后事件循环系统再按照先后顺序执行消息队列中的任务。

    不考虑异步事件,同步事件的流程如下。

    同步队列:
    task_queue
    sync_task1, sync_task2, sync_task3, …

    事件循环:
    getSyncTask; // (sync_task1)
    processSyncTask;
    getSyncTask; // (sync_task2)
    processSyncTask;
    getSyncTask; // (sync_task3)
    processSyncTask;

    引入异步事件的变化是,每次执行完同步任务后,都会去获取异步任务(未必能获取到)

    事件循环的流程如下。

    同步队列:
    task_queue
    sync_task1, sync_task2, sync_task3, …

    延迟队列:
    delay_task_queue
    async_task

    事件循环:
    getSyncTask; // (sync_task1)
    processSyncTask;
    getAsyncTask; // 无结果(如延迟时间未到)

    getSyncTask; // (sync_task2)
    processSyncTask;
    getAsyncTask; // (async_task)
    processAsyncTask; // 执行异步事件

    getSyncTask; // (sync_task3)
    processSyncTask;
    getAsyncTask; // 无异步事件;

    当通过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会创建一个回调任务,包含了回调函数 name、currentTime、delayTime;
    创建好回调任务之后,再将该任务添加到延迟执行队列中;

    还要重点关注getAsyncTask以及processAsyncTask的执行时机,在上面的示例中,处理完同步队列中的一个任务之后,就开始执行getAsyncTask 函数。getAsyncTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。如果同步任务的时间过久,那异步任务的执行事件未必会远大于设置延迟时间。