有时一个函数并不需要立刻执行,而是等待特定一段时间之后再执行。这就是所谓的“计划调用(scheduling a call)
setTimeout() 是延时器,setInterval() 是定时器
- setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。
- setInterval 允许我们重复运行一个函数,从一段时间间隔之后开始运行,之后以该时间间隔连续重复运行该函数,直到定时器或者窗口关闭。
setTimeout
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
参数说明:
- func|code:想要执行的函数或代码字符串。 一般传入的都是函数。由于某些历史原因,支持传入代码字符串,但是不建议这样做。
- delay:执行前的延时,以毫秒为单位(1000 毫秒 = 1 秒),默认值是 0;
- arg1,arg2…:要传入被执行函数(或代码字符串)的参数列表(IE9 以下不支持)
setTimeout(() => {
console.log('1', 1)
}, 100)
const getView = (value) => {
console.log('1', 1)
if (value) {
console.log('1', value)
}
}
setTimeout(getView, 100, 'hdud')
返回值:返回值 timeoutID 是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。
提醒:
setTimeout() 只执行 code 一次。如果要多次调用,请使用 setInterval() 或者让 code 自身再次调用 setTimeout()。
用 clearTimeout 清除定时器
setTimeout 在调用时会返回一个“定时器标识符(timer identifier)”
- 在浏览器中,定时器标识符是一个数字。在其他环境中,可能是其他的东西。例如 Node.js 返回的是一个定时器对象,这个对象包含一系列方法。
let timerId = setTimeout(...);
clearTimeout(timerId);
const getView = (value) => {
console.log('1', 1)
if (value) {
console.log('1', value)
}
}
const timeID = setTimeout(getView, 100, 'hdud')
console.log('first', timeID)//2430
clearTimeout(timeID)
setInterval
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
所有参数的意义和setTimeout也是相同的。不过与 setTimeout 只执行一次不同,setInterval 是每间隔给定的时间周期性执行。
const t = setInterval(() => console.log('1', 1), 1000)
setTimeout(() => { clearInterval(t); console.log('stop') }, 5000)
- alert 弹窗显示的时候计时器依然在进行计时
-
- 在大多数浏览器中,包括 Chrome 和 Firefox,在显示 alert/confirm/prompt 弹窗时,内部的定时器仍旧会继续“嘀嗒”。下次关闭会正常
嵌套的 setTimeout
周期性调度有两种方式。
一种是使用 setInterval,另外一种就是嵌套的 setTimeout,就像这样
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
- 嵌套的 setTimeout 要比 setInterval 灵活得多。采用这种方式可以根据当前执行结果来调度下一次调用,因此下一次调用可以与当前这一次不同。
- 嵌套的 setTimeout 相较于 setInterval 能够更精确地设置两次执行之间的延时。
- setInterval,在实际调用的时候可能因为func执行的速度而变快变慢,嵌套的 setTimeout 就能确保延时的固定(这里是 100 毫秒)。所有的调度方法都不能 保证 确切的延时,下面的因素可能都会导致变慢,因为 JavaScript 其实是运行在单线程环境中的,这就意味着定时器仅仅是计划代码在未来的某个时间执行,而具体执行时机是不能保证的
-
- CPU 过载。
- 浏览器页签处于后台模式。
- 笔记本电脑用的是省电模式。
比如:
实现一个服务(server),每间隔 5 秒向服务器发送一个数据请求,但如果服务器过载了,那么就要降低请求频率,比如将间隔增加到 10、20、40 秒等,如果我们调度的函数占用大量的 CPU,那么我们可以测量执行所需要花费的时间,并安排下次调用是应该提前还是推迟
let delay = 5000;
let timerId = setTimeout(function request() {
...发送请求...
if (request failed due to server overload) {
// 下一次执行的间隔是当前的 2 倍
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
垃圾回收和 setInterval/setTimeout 回调(callback)
当一个函数传入 setInterval/setTimeout 时,将为其创建一个内部引用,并保存在调度程序中。这样,即使这个函数没有其他引用,也能防止垃圾回收器(GC)将其回收,对于 setInterval,传入的函数也是一直存在于内存中,直到 clearInterval 被调用。
// 在调度程序调用这个函数之前,这个函数将一直存在于内存中
setTimeout(function() {...}, 100);
- 这里还要提到一个副作用。如果函数引用了外部变量(译注:闭包),那么只要这个函数还存在,外部变量也会随之存在。它们可能比函数本身占用更多的内存。因此,当我们不再需要调度函数时,最好取消它,即使这是个(占用内存)很小的函数。
零延时的 setTimeout
这儿有一种特殊的用法:setTimeout(func, 0),或者仅仅是 setTimeout(func)。这样调度可以让 func 尽快执行。但是只有在当前正在执行的脚本执行完成后,调度程序才会调用它。
也就是该函数被调度在当前脚本执行完成“之后”立即执行
setTimeout(() => console.log("World"));
console.log("Hello");
//Hello
//World
- 零延时实际上不为零(在浏览器中),浏览器会将 setTimeout 或 setInterval 的五层或更多层嵌套调用(调用五次之后)的最小延时限制在 4ms。这是历史遗留问题。