JavaScript设置定时器、取消定时器及执行机制解析

今天整理了一下 JavaScript 定时器,顺便了解了一下 JavaScript 的运行机制,现在记录一下。

06020643_062L.jpg JavaScript设置定时器、取消定时器及执行机制解析 HTML笔记

JavaScript 执行机制

浏览器( JavaScript 引擎)执行 JavaScript 的机制是基于事件循环的。由于 JavaScript 是单线程,同一时间只能执行一个任务。为了避免某些长时间任务造成无意义等待,JavaScript 引入了异步概念。

同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程。等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如 ajax 请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环。

JavaScript 定时器

定时器也是一种异步任务,通常浏览器都有一个独立的定时器模块,定时器的延迟时间就由定时器模块来管理,当某个定时器到了可执行状态,就会被加入主线程队列。

了解了上面的执行机制,我们不难理解 JavaScript 定时器不是绝对精准的,延迟的时间严格来说总是大于我们设定的时间的,至于大多少就要看当时 JavaScript 的执行情况了。

另外,多个定时器如不及时清除clearTimeout()),会造成干扰,使延迟时间更加捉摸不透。所以,不管定时器有没有执行完,要及时清除不需要的定时器。

下面来介绍几个常用的 JavaScript 定时器:

setTimeout()

设置一个定时器,在定时器到期后执行一次函数或代码段:setTimeout(fn, x) 表示延迟 x 毫秒之后执行 fn

var timeoutId = setTimeout(code, delay, param1, param2, ...)
var timeoutId = setTimeout(func, delay, param1, param2, ...)

timeoutId 定时器ID

func 延迟后执行的函数

code 延迟后执行的代码字符串,不推荐使用原理类似eval()

delay 延迟的时间(单位:毫秒),默认值为0

param1,param2 向延迟函数传递而外的参数,IE9以上支持

HTML5 规范规定最小延迟时间不能小于 4ms ,即 x 如果小于 4 ,会被当做 4 来处理。 不过不同浏览器的实现不一样,比如,Chrome可以设置1ms,IE11/Edge是4ms。

另外, setTimeout() 方法不是 Ecmascript 规范定义的内容,而是属于BOM提供的功能。setTimeout() 延迟也是有上限的,如果大于 2  的 31 次方的话,会立即执行,延迟无效。

setInterval()

以固定的时间间隔重复调用一个函数或者代码段:

var intervalId = window.setInterval(func, delay , param1, param2, ...]);
var intervalId = window.setInterval(code, delay);

intervalId 重复操作的ID

func 延迟调用的函数

code 代码段

delay 延迟时间,没有默认值

setInterval 的实现机制跟 setTimeout 类似,只不过是重复执行的。

对于 setInterval(fn, 100) 容易产生一个误区:并不是上一次 fn 执行完了之后再过 100ms 才开始执行下一次 fn。 事实上,setInterval 并不管上一次 fn 的执行结果,而是每隔 100ms 就将 fn 放入主线程队列,而两次 fn 之间具体间隔多久就不一定了,跟 setTimeout 实际延迟时间类似,和 JavaScript 执行情况有关。

(function testSetInterval() {
    let i = 0;
    const start = Date.now();
    const timer = setInterval(() => {
        i += 1;
        i === 5 && clearInterval(timer);
        console.log(`第${i}次开始`, Date.now() - start);
        for(let i = 0; i < 100000000; i++) {}
        console.log(`第${i}次结束`, Date.now() - start);
    }, 100);
})();
/*输出
第1次开始 100
第1次结束 1089
第2次开始 1091
第2次结束 1396
第3次开始 1396
第3次结束 1701
第4次开始 1701
第4次结束 2004
第5次开始 2004
第5次结束 2307 */

setImmediate()

浏览器完全结束当前运行的操作之后立即执行指定的函数(仅 IE10 和 Node 0.10+ 中有实现),类似 setTimeout(func, 0)

var immediateId = setImmediate(func , param1, param2, ...]);
var immediateId = setImmediate(func);

immediateId 定时器ID

func 回调

这算一个比较新的定时器,目前IE11/Edge支持、Nodejs支持,Chrome不支持,其他浏览器未测试。

从API名字来看很容易联想到setTimeout(0),不过setImmediate应该算是setTimeout(0)的替代版。

在IE11/Edge中,setImmediate延迟可以在1ms以内,而setTimeout有最低4ms的延迟,所以setImmediate比setTimeout(0)更早执行回调函数。不过在Nodejs中,两者谁先执行都有可能,原因是Nodejs的事件循环和浏览器的略有差异。

(function testSetImmediate() {
    const label = 'setImmediate';
    console.time(label);
 
    setImmediate(() => {
        console.timeEnd(label);
    });
})();
/* Edge输出:setImmediate: 0.555 毫秒 */

很明显,setImmediate设计来是为保证让代码在下一次事件循环执行,以前setTimeout(0)这种不可靠的方式可以丢掉了。

requestAnimationFrame()

专门为实现高性能的帧动画而设计的API,但是不能指定延迟时间,而是根据浏览器的刷新频率(帧)而定。

var requestId = window.requestAnimationFrame(func);

func 回调

上面简单的介绍了四种 JavaScript 的定时器,而本文将会主要介绍比较常用的两种:setTimeout()setInterval()

clearInterval()、clearTimeout() 取消定时器

clearInterval() 方法可取消由 setInterval() 函数设定的定时执行操作。

clearInterval() 方法的参数必须是由 setInterval() 返回的 ID 值。

myVar = setInterval(func, 200); // 设置一个定时器
clearInterval(myVar); // 取消这个定时器

myVar 调用 setInterval() 函数时所获得的返回值,使用该返回标识符作为参数,可以取消该 setInterval() 所设定的定时执行操作。

clearTimeout() 同上。

举例:

基本用法:

// 下面代码执行之后会输出什么?
var intervalId, timeoutId;

timeoutId = setTimeout(function () {
    console.log(1);
}, 300);

setTimeout(function () {
    clearTimeout(timeoutId);
    console.log(2);
}, 100);

setTimeout('console.log("5")', 400);

intervalId = setInterval(function () {
    console.log(4);
    clearInterval(intervalId);
}, 200);

// 分别输出: 2、4、5

setInterval 和 setTimeout的区别:

// 执行在面的代码块会输出什么?
setTimeout(function () {
    console.log('timeout');
}, 1000);

setInterval(function () {
    console.log('interval')
}, 1000);

// 输出一次 timeout,每隔1S输出一次 interval

/*--------------------------------*/

// 通过setTimeout模拟setInterval 和 setInterval有啥区别么?
var callback = function () {
    if (times++ > max) {
        clearTimeout(timeoutId);
        clearInterval(intervalId);
    }

    console.log('start', Date.now() - start);
    for (var i = 0; i < 990000000; i++) {}
    console.log('end', Date.now() - start);
},
delay = 100,
times = 0,
max = 5,
start = Date.now(),
intervalId, timeoutId;

function imitateInterval(fn, delay) {
    timeoutId = setTimeout(function () {
        fn();

        if (times <= max) {
            imitateInterval(fn ,delay);
        }
    }, delay);
}

imitateInterval(callback, delay);
intervalId = setInterval(callback, delay);

如果是setTimeoutsetInterval的话,它俩仅仅在执行次数上有区别,setTimeout一次、setIntervaln次。

而通过setTimeout模拟的setIntervalsetInterval的区别则在于:setTimeout只有在回调完成之后才会去调用下一次定时器,而setInterval则不管回调函数的执行情况,当到达规定时间就会在事件队列中插入一个执行回调的事件,所以在选择定时器的方式时需要考虑setInterval的这种特性是否会对你的业务代码有什么影响?

setTimeout(func, 0) 和 setImmediate(func)谁更快?(仅仅是好奇,才写的这段测试)

console.time('immediate');
console.time('timeout');

setImmediate(() => {
    console.timeEnd('immediate');
});

setTimeout(() => {
    console.timeEnd('timeout');
}, 0);
}, 0);

Node.JS v6.7.0中测试发现setTimeout更早执行。

赞 (0)
分享到: +

评论 沙发

Avatar

换个身份

  • 昵称 (必填)
  • 邮箱 (选填)