首页
JS 异步编程(2):setTimeout 与 setInterval

https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals

  • setTimeout - 在指定的时间后执行一段代码

  • setInterval - 以固定的时间间隔,重复运行一段代码

  • requestAnimationFrame - setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行.

这些函数设置的异步代码实际上在主线程上运行(在其指定的计时器过去之后)。

setTimeout

setTimeout的语法格式如下:

var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID = scope.setTimeout(function[, delay]);
var timeoutID = scope.setTimeout(code[, delay]);

function 可以是匿名函数,也可以直接传递函数名。code是使用字符串的方式传递函数,这种方式不推荐,原因和eval一样,是不安全的。arg是传递给function的参数

const timeoutId = setTimeout(() => {
  console.log(123)
}, 3000)

console.log(timeoutId)

function log(msg) {
  console.log(`log: ${msg}`)
}

setTimeout(log, 3000, '打印日志')

clearTimeout可以在超时前清除掉定时器。在之后清除是没有任何意义的,也就是说setTimeout是不需要clearTimeout的,除非是特意想取消。

setInterval

setInterval的语法格式如下:

var intervalID = scope.setInterval(func, delay, [arg1, arg2, ...]);
var intervalID = scope.setInterval(code, delay);

setInterval必须使用clearInterval清除,不然会一直执行下去,导致内存泄露。

关于 setTimeout() 和 setInterval() 需要注意的几点

  • 使用递归调用setTimeout来替代setInterval

为什么有时候用 setTimeout 替代 setInterval?
不要再用 setInterval 做轮询了!

setInterval有一个缺陷,就是当你的代码有可能比你分配的时间间隔,花费更长时间运行。前一个任务没有执行完,然后又开始执行后一个任务,会导致一些异常问题。对于这种情况,可以使用递归调用setTimeout来避免这个问题。

let i = 1

setTimeout(function run() {
  console.log(i)
  i++
  if (i == 10) {
    return
  }
  setTimeout(run, 1000)
}, 1000)
  • 关于最小延时>=4ms 的问题

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的setInterval的回调函数阻塞导致的。很多人会把这个跟立即超时搞混,需要注意。

  • 立即超时
setTimeout(function() {
  alert('World');
}, 0);

alert('Hello');

立即超时它不会立即执行。因为任何异步代码仅在主线程可用后才执行(换句话说,当调用栈为空时)。这个涉及到了 JS 事件循环的机制,在事件循环中专门再讲解。

使用 setTimeout 实现一个轮询器

/**
 * 使用setTimeout替代setInterval实现的轮询功能
 */
export default class Polling {
    constructor() {
        this.func = null
        //是否停止定时器
        this.stopFlag = false
    }

    /**
     * 使用setTimeout轮询
     * @param func
     * @param ms
     */
    start(func, ms) {
        if (this.func === null) {
            this.func = func
        }
        // 确保一个 Timer 实例只能重复一个 func
        if (this.func !== func) {
            return
        }
        if (!this.stopFlag) {
            setTimeout(()=>{
                func()
                this.start(func, ms)
            }, ms)
        }
    }

    /**
     * 停止轮询
     */
    stop() {
        this.stopFlag = true
        this.func = null
    }
}

使用方式:

const polling = new Polling()
const func = ()=>{
	//检查Dom是否存在
	const divTask = $("#task_" + data.task_id);
	if (!divTask.length) {
			return;
	}
	//Dom存在,结束继续poll
	polling.stop()
  //执行操作
	//......
}
polling.start(func, 100)