【转向Javascript系列】从setTimeout说事件循环模型

作为一个从其他编程语言(C#/Java)转到Javascript的开发人员,在学习Javascript过程中,setTimeout()方法的运行原理是我遇到的一个不太好理解的部分,本文尝试结合其他编程语言的实现,从setTimeout说事件循环模型

1.从setTimeout说起

setTimeout()方法不是ecmascript规范定义的内容,而是属于BOM提供的功能。查看w3school对setTimeout()方法的定义,setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

语法setTimeout(fn,millisec),其中fn表示要执行的代码,可以是一个包含javascript代码的字符串,也可以是一个函数。第二个参数millisec是以毫秒表示的时间,表示fn需推迟多长时间执行。

调用setTimeout()方法之后,该方法返回一个数字,这个数字是计划执行代码的唯一标识符,可以通过它来取消超时调用。

起初我对 setTimeout()的使用比较简单,对其运行机理也没有深入的理解,直到看到下面代码

1

2

3

4

5

6

var start = new Date;

setTimeout(function(){

var end = new Date;

console.log('Time elapsed:', end start, 'ms');

}, 500);

while (new Date start < 1000) {};

在我最初对setTimeout()的认识中,延时设置为500ms,所以输出应该为Time elapsed: 500 ms。因为在直观的理解中,Javascript执行引擎,在执行上述代码过程中,应当是一个由上往下的顺序执行过程,setTimeout函数是先于while语句执行的。可是实际上,上述代码运行多次后,输出至少是延迟了1000ms。

2.Java对setTimeout的实现

联想起以往学习Java的经验,上述Javascript的setTimeout()让我困惑。Java对setTimeout的实现有多种API实现,这里我们以java.util.Timer包为例。使用Timer在Java中实现上述逻辑,运行多次,输出都是Time elapsed: 501 ms。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

import java.util.Date;

import java.util.Timer;

import java.util.TimerTask;

 

public class TimerTest {

 

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        long start = System.currentTimeMillis();

        Timer timer = new Timer();

        timer.schedule(new MyTask(start), 500);

        while (System.currentTimeMillis() start < 1000) {};

    }

 

}

 

class MyTask extends TimerTask {

    private long t;

 

    public MyTask(long start) {

        // TODO Auto-generated constructor stub

        t=start;

    }

 

    @Override

    public void run() {

        // TODO Auto-generated method stub

        long end = System.currentTimeMillis();

        System.out.println("Time elapsed:"+(end this.t)+ "ms");

    }

    

}

这里深究setTimeout()为什么出现这一差异之前,先说说java.util.Timer的实现原理。

上述代码几个关键要素为Timer、TimerTask类以及Timer类的schedule方法,通过阅读相关源码,可以了解其实现。

Timer:一个Task任务的调度类,和TimerTask任务一样,是供用户使用的API类,通过schedule方法安排Task的执行计划。该类通过TaskQueue任务队列和TimerThread类完成Task的调度。

TimerTask:实现Runnable接口,表明每一个任务均为一个独立的线程,通过run()方法提供用户定制自己任务。

TimerThread:继承于Thread,是真正执行Task的类。

TaskQueue:存储Task任务的数据结构,内部由一个最小堆实现,堆的每个成员为TimeTask,每个任务依靠TimerTask的 nextExecutionTime属性值进行排序,nextExecutionTime最小的任务在队列的最前端,从而能够现实最早执行。

Timer

 

3.根据结果找原因

看过了Java.util.Timer对类似setTimeout()的实现方案,继续回到前文Javascript的setTimeout()方法中,再来看看之前的输出为什么与预期不符。

1

2

3

4

5

6

var start = new