JavaScript 工作线程实现方式
本帖被 xupy 执行置顶操作(2011-05-30)
在 Ajax 应用中,有时候会需要在后台执行一些耗时较长,但是与页面主要逻辑无关的操作。比如对于一个在线文档编辑器来说,会需要定期的自动备份用户当前所编辑的内容,这样当应用异常崩溃时,用户还能恢复他所编辑的内容。这样的定期备份任务可能会需要花费一些时间,但是优先级较低。类似这样的任务还有页面内容的预先加载和日志记录等。对于这些任务,最好的实现方式是在后台工作线程中执行,这样不会对用户在主页面上的操作造成影响。用户并不会希望由于后台备份正在进行,而无法对当前的文档进行编辑。
这里使用的名词“JavaScript 工作线程”其实并不严谨,不过可以提供与操作系统中的线程概念相关的类比,从而更加容易理解其背后的动机和实现方式。本文会介绍三种 JavaScript 工作线程的实现方式,分别是 setTimeout()、*** Gears 和 Web Worker。首先从 JavaScript 中的定时器 setTimeout()开始。
使用 setTimeout()
浏览器中 JavaScript 引擎是单线程执行的。也就是说,在同一时间内,只能有一段代码被 JavaScript 引擎执行。如果同一时间还有其它代码需要执行的话,则这些代码需要等待 JavaScript 引擎执行完成当前的代码之后才有可能获得被执行的机会。正常情况下,作为页面加载过程中的重要一步,JavaScript 引擎会顺序执行页面上的所有 JavaScript 代码。当页面加载完成之后,JavaScript 引擎会进入空闲状态。用户在页面上的操作会触发一些事件,这些事件的处理方法会交给 JavaScript 引擎来执行。由于 JavaScript 引擎的单线程特性,一般会在内部维护一个待处理的事件队列。每次从事件队列中选出一个事件处理方法来执行。如果在执行过程中,有新的事件发生,则新事件的处理方法只会被加入到队列中等待执行。如果当前正在执行的事件处理方法非常耗时,则队列中的其它事件处理方法可能长时间无法得到执行,造成用户界面失去响应,严重影响用户的使用体验。
JavaScript 引擎的这种工作方式类似于早期的单核 CPU 的调度方式。单核 CPU 虽然也是支持多任务同时运行的,但是实际上同一时间只能有一个任务在执行。CPU 通过时间片的轮转来保证每个任务都有一定的执行时间。JavaScript 并没有原生提供与操作系统中的线程类似的结构,但是可以通过所提供的定时器机制来模拟。JavaScript 提供了两个基本的方法用来执行与定时相关的操作,分别是 setTimeout()和 setInterval()。
setTimeout() 和 setInterval()
JavaScript 中的 setTimeout()用来设置在指定的间隔时间之后执行某个 JavaScript 方法。 setTimeout()的方法声明非常简单: setTimout(func, time),其中参数 func表示的是要执行的 JavaScript 方法,可以是 JavaScript 方法对象或是方法体的字符串;参数 time表示的是以毫秒为单位的间隔时间。 setInterval()方法用来设置根据指定的间隔重复执行某个 JavaScript 方法。 setInterval()的方法声明与 setTimeout()相同: setInterval(func, time),这里参数 time指定的是方法 func重复执行的间隔。当 setTimeout()或 setInterval()被调用的时候,浏览器会根据设置的时间间隔来触发相应的事件。
假如代码的调用方式是 setTimeout(func, 100),那么该代码被执行 100 毫秒之后,定时器的事件被触发。如果这个时候 JavaScript 引擎没有正在执行的其它代码,则与此定时器对应的 JavaScript 方法 func就可以被执行。否则的话,该 JavaScript 方法的执行就被加入到等待的队列中。当 JavaScript 引擎空闲的时候,会从这个队列中选择一个等待的 JavaScript 方法来执行。也就是说,虽然在调用 setTimeout()的时候设置的间隔时间是 100 毫秒,与之对应的 JavaScript 方法实际被执行的间隔有可能大于设定的 100 毫秒,取决于是否有其它代码正在被执行和执行所花费的时间。因此 setTimeout()的实际生效的间隔时间可能大于设定的时间。
而 setInterval()的执行方式与 setTimeout()有很大不同。假如代码的调用方式是 setInterval(func, 100),则每隔 100 毫秒,定时器的事件就会被触发。与 setTimeout()相同的是,如果当前 JavaScript 引擎空闲的话,则定时器对应的方法 func会被立即执行。否则的话,该 JavaScript 方法的执行就会被加入到等待队列中。由于定时器的事件是每隔 100 毫秒就触发一次,有可能某一次事件触发的时候,上一次事件的处理方法还没有机会得到执行,仍然在等待队列中。这个时候,这个新的定时器事件就被丢弃。需要注意的是,由于 JavaScript 引擎的这种执行方式,有可能定时器事件处理方法的两次被执行的实际时间间隔小于设定的间隔。比如上一个定时器事件的处理方法触发之后,等待了 50 毫秒才获得被执行的机会。而第二个定时器事件的处理方法被触发之后,马上就被执行了。那么这两者之间的时间间隔实际上只有 50 毫秒。因此, setInterval()并不适合实现精确的按固定间隔的调度操作。
总的来说,使用 setTimeout()和 setInterval()的时候,都不能满足精确的时间间隔。通过 setTimeout()设置的 JavaScript 方法的实际执行间隔时间不小于设定的时间,而通过 setInterval()设置的重复执行的 JavaScript 方法的间隔可能会小于设定的时间。
使用 setTimeout()实现工作线程
前面提到, setTimeout()可以设置在某个时间间隔之后再执行某个 JavaScript 方法。 setTimeout()的另外一个作用是可以将某些操作推迟执行,让出 JavaScript 引擎来处理其等待队列中的其它事件,以提高用户体验。比如某个操作由于需要进行大量计算,平均耗时在 3 秒左右。当这个操作开始执行之后,就会一直占用 JavaScript 引擎,直到执行结束。在这个过程中,其它的 JavaScript 方法就被放置到 JavaScript 引擎的等待队列中。比如,如果用户在这个过程中点击了页面上的某个按钮,则相
这里使用的名词“JavaScript 工作线程”其实并不严谨,不过可以提供与操作系统中的线程概念相关的类比,从而更加容易理解其背后的动机和实现方式。本文会介绍三种 JavaScript 工作线程的实现方式,分别是 setTimeout()、*** Gears 和 Web Worker。首先从 JavaScript 中的定时器 setTimeout()开始。
使用 setTimeout()
浏览器中 JavaScript 引擎是单线程执行的。也就是说,在同一时间内,只能有一段代码被 JavaScript 引擎执行。如果同一时间还有其它代码需要执行的话,则这些代码需要等待 JavaScript 引擎执行完成当前的代码之后才有可能获得被执行的机会。正常情况下,作为页面加载过程中的重要一步,JavaScript 引擎会顺序执行页面上的所有 JavaScript 代码。当页面加载完成之后,JavaScript 引擎会进入空闲状态。用户在页面上的操作会触发一些事件,这些事件的处理方法会交给 JavaScript 引擎来执行。由于 JavaScript 引擎的单线程特性,一般会在内部维护一个待处理的事件队列。每次从事件队列中选出一个事件处理方法来执行。如果在执行过程中,有新的事件发生,则新事件的处理方法只会被加入到队列中等待执行。如果当前正在执行的事件处理方法非常耗时,则队列中的其它事件处理方法可能长时间无法得到执行,造成用户界面失去响应,严重影响用户的使用体验。
JavaScript 引擎的这种工作方式类似于早期的单核 CPU 的调度方式。单核 CPU 虽然也是支持多任务同时运行的,但是实际上同一时间只能有一个任务在执行。CPU 通过时间片的轮转来保证每个任务都有一定的执行时间。JavaScript 并没有原生提供与操作系统中的线程类似的结构,但是可以通过所提供的定时器机制来模拟。JavaScript 提供了两个基本的方法用来执行与定时相关的操作,分别是 setTimeout()和 setInterval()。
setTimeout() 和 setInterval()
JavaScript 中的 setTimeout()用来设置在指定的间隔时间之后执行某个 JavaScript 方法。 setTimeout()的方法声明非常简单: setTimout(func, time),其中参数 func表示的是要执行的 JavaScript 方法,可以是 JavaScript 方法对象或是方法体的字符串;参数 time表示的是以毫秒为单位的间隔时间。 setInterval()方法用来设置根据指定的间隔重复执行某个 JavaScript 方法。 setInterval()的方法声明与 setTimeout()相同: setInterval(func, time),这里参数 time指定的是方法 func重复执行的间隔。当 setTimeout()或 setInterval()被调用的时候,浏览器会根据设置的时间间隔来触发相应的事件。
假如代码的调用方式是 setTimeout(func, 100),那么该代码被执行 100 毫秒之后,定时器的事件被触发。如果这个时候 JavaScript 引擎没有正在执行的其它代码,则与此定时器对应的 JavaScript 方法 func就可以被执行。否则的话,该 JavaScript 方法的执行就被加入到等待的队列中。当 JavaScript 引擎空闲的时候,会从这个队列中选择一个等待的 JavaScript 方法来执行。也就是说,虽然在调用 setTimeout()的时候设置的间隔时间是 100 毫秒,与之对应的 JavaScript 方法实际被执行的间隔有可能大于设定的 100 毫秒,取决于是否有其它代码正在被执行和执行所花费的时间。因此 setTimeout()的实际生效的间隔时间可能大于设定的时间。
而 setInterval()的执行方式与 setTimeout()有很大不同。假如代码的调用方式是 setInterval(func, 100),则每隔 100 毫秒,定时器的事件就会被触发。与 setTimeout()相同的是,如果当前 JavaScript 引擎空闲的话,则定时器对应的方法 func会被立即执行。否则的话,该 JavaScript 方法的执行就会被加入到等待队列中。由于定时器的事件是每隔 100 毫秒就触发一次,有可能某一次事件触发的时候,上一次事件的处理方法还没有机会得到执行,仍然在等待队列中。这个时候,这个新的定时器事件就被丢弃。需要注意的是,由于 JavaScript 引擎的这种执行方式,有可能定时器事件处理方法的两次被执行的实际时间间隔小于设定的间隔。比如上一个定时器事件的处理方法触发之后,等待了 50 毫秒才获得被执行的机会。而第二个定时器事件的处理方法被触发之后,马上就被执行了。那么这两者之间的时间间隔实际上只有 50 毫秒。因此, setInterval()并不适合实现精确的按固定间隔的调度操作。
总的来说,使用 setTimeout()和 setInterval()的时候,都不能满足精确的时间间隔。通过 setTimeout()设置的 JavaScript 方法的实际执行间隔时间不小于设定的时间,而通过 setInterval()设置的重复执行的 JavaScript 方法的间隔可能会小于设定的时间。
使用 setTimeout()实现工作线程
前面提到, setTimeout()可以设置在某个时间间隔之后再执行某个 JavaScript 方法。 setTimeout()的另外一个作用是可以将某些操作推迟执行,让出 JavaScript 引擎来处理其等待队列中的其它事件,以提高用户体验。比如某个操作由于需要进行大量计算,平均耗时在 3 秒左右。当这个操作开始执行之后,就会一直占用 JavaScript 引擎,直到执行结束。在这个过程中,其它的 JavaScript 方法就被放置到 JavaScript 引擎的等待队列中。比如,如果用户在这个过程中点击了页面上的某个按钮,则相