Javascript setInterval与setTimeout详解
1. Javascript运行机制
Javascript是单线程语言,同步任务在主线程队列中按顺序依次执行,这就意味着后一个任务必须等待前一个任务执行完毕后才开始执行。由此就出现了某一任务执行时间过长而造成的后续任务的等待问题。因此Javascript引入了异步的概念,它允许用另一个线程来管理异步任务,当某个异步任务达到可执行的条件时,便将该任务放在主线程队列中。
2. Javascript间歇调用和超时调用
Javascript提供间歇调用setInterval和超时调用setTimeout两种方法来实现异步编程。setInterval是指每隔指定的时间执行一次代码,setTimeout则是指在指定的时间过后执行代码。
setTimeout( )方法
该方法接受两个参数:要执行的代码和以毫秒为单位的时间。如下代码,会在1s后打印出’Hello World!’。
setTimeout(function () {
console.log('Hello World!');
}, 1000);
虽然话说第二个参数表示要等待的时长,但现实可能等待的时间要大于这个时长。第二个参数指定了过多少毫秒把当前任务放到主线程队列中,此时如果队列为空,那么该代码会立即执行;如果队列不空,那么就要等前面的代码执行完后才可以执行。如下代码,我们会看到,虽然我们将时间设为0,但打印结果却不是World Hello,而是Hello World,这是由于打印world的代码准备执行的时候主线程队列不为空,所以结果是Hello World。
setTimeout(function () {
console.log('World');
}, 0);
console.log('Hello');
setInterval( )方法
该方法和setTimeout实现机制类似,只不过它会按照指定的时间间隔重复执行。也接受两个参数:要执行的代码(字符串或函数)和每次执行之前需要等待的毫秒数。如下代码,每隔1s就将要执行的代码放到主线程队列中。
setInterval(function () {
console.log('Hello World!');
}, 1000);
这里需要注意的是,好多人会误认为第二个参数代表的时间间隔是指,在上一次代码执行完成后需要等待的时间间隔,但事实并非如此,不管上一次代码执行结果怎样,都会每隔固定的毫秒数将要执行的代码放到主线程队列中。
取消setTimeout和setInterval
调用setTimeout和setInterval都会返回一个数值ID,通过使用该ID可以取消超时调用和间歇调用。方法clearTimeout( )可以完全取消超时调用,方法clearInterval( )可以取消尚未执行的间歇调用。如下代码,变量i每隔1s递增一次并被打印,当i等于5时,间歇调用取消。
var i = 0;
var max = 5;
var intervalId = null;
function count() {
i++;
console.log(i);
if (i === max) {
clearInterval(intervalId);
console.log('done');
}
}
intervalId = setInterval(count, 1000);
3. 使用setTimeout模拟setInterval
一般在开发中,很少真正的使用setInterval,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动。这就意味着我们某些次数的代码的执行可能会丢失。为了避免这一点,我们可以用setTimeout来模拟实现setInterval。那么怎样让setTimeout循环起来呢,这就用到了递归的思想。如下代码:函数foo内部实现了自身的递归,这样每隔1s函数foo就会执行一次。
var i = 0;
function foo() {
i++;
console.log(i);
setTimeout(foo, 1000);
}
foo();
这种模拟setInterval的方法与setInterval本身还是有一些区别的。
第一,我们之前说到了setInterval会每隔固定的时长将要执行的代码放到主线程队列中,不管上一次代码执行结果如何;但使用setTimeout模拟的方法并非如此,此递归方法会在上一次代码执行结束那一刻,等待需要的时长,再开始下一次代码的执行。
第二,setInterval的第一次间隔调用前是要等待所需要的时长的,换句话说就是要等待相应的毫秒数才会第一次执行代码,但以上模拟的方法中第一次代码的执行是立即的。当然我们也可以通过改造上诉模拟方法来避免这个差异。代码如下:
var i = 0;
function foo() {
function func() {
i++;
console.log(i);
}
setTimeout(function () {
foo();
func();
}, 1000);
}
foo();
因此,尽量避免使用setInterval,而是选择用setTimeout来模拟setInterval。