4

Author: bugall
Wechat: bugallF
Email: 769088641@qq.com
Github: https://github.com/bugall

一: 事件循环

虽然我们用Javascript总是可以实现一些异步代码, 但是Javascript中真正的异步概念,但是直到ES6,Javascript才内建了直接的异步概念。

对于原有Javascript引擎来说, 它只关心如何去执行给定的代码块, 对于什么时候该执行哪些代码块这个是引擎不关心的。引擎是依赖于宿主环境的,这里的宿主环境并不是指操作系统环境,因为不同的平台提供的“可执行环境”不同。而宿主环境就是为了隔离代码、语言与具体的平台而提出的一个设计。比如web浏览器环境,Node.js这样的工具等,所有的这些环境都有一个共同点,它们都提供了一种机制来处理程序中多个代码块的执行,且执行每块时调用Javascript引擎,这种机制被成为事件循环

换句话说,Javascript引擎本身并没有时间的概念,只是一个按需执行Javascript任意代码片段的环境。“事件”的调度总是由包含它的环境进行。

注意!!!,ES6后事件的管理方式有所改变。ES6本身解决的事件在哪里管理的问题,现在ES6精确指定了事件循环的工作细节,这就 意味着技术上将其纳如了Javascript引擎的势力范围,而不再由宿主环境管理,这个改变的一个主要原因是ES6中Promise的引入,这个技术要求事件循环队列的调度运行能够直接进行精细的控制

我们看下面的这段代码:

function task() {
    console.log("Hello Word");
}
setTimeout(task, 1000);

如果你在代码中设置一个计时器, 当计时器到达指定的时间后执行函数task, 当Javascript引擎执行到定时器的时候会通知宿主环境:“我要去做别的事情, 等1秒后就调用task函数,注意:是调用而不是执行)

我们用一段伪代码实现一个简单的事件循环

var eventList = [event1, event2, event3];
var event;
while(true) {
    if (eventList.length > 0 ) {
        event = eventList.pop();
    }
    run(event);
}

可以看到有一个用while循环实现的持续运行的循环,当event1, event2, event3都取出被执行一次后称为一轮,循环的每一轮称为一个tick,对于每一个tick而言,如果在队列中有等待的事件,那么就会从队列中取出下一个事件并执行,这些事件就是我们代码中写的回调函数。

需要注意的是,定时器比较特殊,setTimeout(task, 1000)并没有把回调函数挂在事件循环队列中,它所做的就是设置一个定时器,当定时器到时后,环境会把你的回调函数放在事件循环中,这样,在未来某个时刻的tick会被取出执行。

如果你的事件循环中已经有很多项目后,定时器的回调就要被放到队尾( 不支持抢占式 )等待被执行,这也就是定时器不准的原因。定时器的回调函数的执行要根据时间队列的状态而定。
那么该如何去降低定时器误差呢?

二:任务队列

严格来说,定时器并不直接把回调函数直接插到事件循环队列,定时器会在有机会的时候插入事件,对于连续的两个setTimeout(..., 0)调用不能保证会严格按照调用顺序处理,所以各种情况都会发生,比如定时器漂移,这类结果是不可预测的,在Node.js中可以用process.nextTick(...)。但是不能保证所有环境都能控制异步的顺序。

在ES6中,在事件循环队列上有个一个新的概念,那就是任务队列,这个概念给大家带来的最大影响可能就是Promise的异步特性

任务队列就是挂在时间循环队列的每个tick之后的一个队列,在事件循环的每个tick中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前tick的任务队列末尾添加一个任务。
一个任务可能引起更多任务被添加到同一个队列末尾,所以理论上说,任务循环可能无限循环,无法转移到下一个事件循环tick.

任务队列的概念,那么怎么减少定时器的误差?看代码:

function task() {
    console.log("Hello Word");
}
setTimeout(task, 1000);

如果现在时间队列中有100个等待被执行的任务,这时候task任务准备插入到事件队列。

没有引入任务队列前:
task会被插入到当前事件循环队列的末端,等待下次的tick被执行,那么这就需要等到当前的tick被执行完,那么这时候的timer延时就决定于100个等待执行的任务耗时。

引入任务队列之后:
直接插入到当前tick的任务队列被执行


李腾飞
889 声望13 粉丝

我是一只小程序猿