前言
setTimeout
和setInterval
都可以用于设置定时器,参数相似,但是使用效果和用途截然不同。本文就重点阐述两者之间的区别
使用方法
setTimeout
setTimeout()
指的是设置一个定时器,并且在延迟时间到期后执行一个函数或者一段代码,该方法会返回一个递增的正整数,表示定时器的编号。在同一个对象(一般指的是window或者worker)上调用setTimeout()
,其编号共用一个编号池,并不会重复,可以使用clearTimeout()
方法将传入的定时器编号对应的定时器清除。
let id = setTimeout(() => console.log(1 + 2), 100);
console.log(id);
clearTimeout(id);
语法
var timeoutId = setTimeout(function[, delay, args...]);
使用setTimeout
必须传入一个函数或者一个字符串代码以便于定时器到期后执行,不推荐使用字符串代码的方式,因为这种方式类似于使用eval()
,存在安全风险。延迟时间delay
可选,单位为毫秒,默认为0,表示”尽快执行“;如果指定了delay
,则定时器将会在延迟时间到期后”尽快执行“。
这里说的”尽快执行“,而不是”立即执行“,是因为除了浏览器默认的最小延迟时间,还有之前提到的事件循环机制。举个栗子:
setTimeout(() => console.log('setTimeout 10ms'), 10);
new Promise((resolve, reject) => {alert('promise alert');resolve()}); // Promise会优先执行,然后才是setTimeout
setInterval
setInterval()
也是设置一个定时器,不同的是,它会在固定的时间间隔内重复调用一个函数或者一段代码,该方法也会返回一个正整数,表示定时器的编号,可以使用clearInterval()
方法来清理定时器。
setInterval()
重点在固定间隔、重复执行,setTimeout()
重点在延后执行。
let i = 0;
let id = setInterval(() => {
i++;
console.log(i);
}, 1000); // 每隔1秒,打印i
clearInterval(id);
语法
var intervalId = setInterval(function[, delay, args...]);
参数形式上和setTimeout()
类似,需要注意的是在设置delay时,setInterval
需要考虑函数执行的时间要小于固定时间间隔。比如说,函数执行需要2s才能完成,如果设置delay为1s,那么setInterval
会一直排队执行,可能得不到想要的结果。
setInterval(() => {
// 函数执行逻辑,耗时2s
}, 1000);
这种情况下,我们可以考虑使用setTimeout
来改造一下,setTimeout
可设置的延迟时间范围很大(32位有符号整数)。
(function loop() {
setTimout(function() {
// 函数执行逻辑
loop(); // 函数执行完后,重复调用
}, delay); // 指定延时时间
})(); // 自执行函数,第一次执行并开始循环调用
// 对比
function loop() {
// 函数执行逻辑
}
setInterval(loop(), delay);
this指向问题
不管是使用setTimeout
还是使用setInterval
,都不可避免会遇到this的指向问题。
setTimeout的this指向问题
setTimeout()
的回调函数里面,this
默认指向的是window对象。
setTimeout(function () {console.log(this)}, 0); // this-->window
这也就意味着,如果我们要在回调函数里面通过this访问对象,有可能会出现undefined
。
let obj = {
name: 'obj'
}
setTimeout(function() {
console.log(obj.name); // obj
})
setTimeout(function() {
console.log(this.obj?.name, obj.name); // undefined,obj
})
let myArray = ["zero", "one", "two"];
myArray.myMethod = function (sProperty) {
alert(arguments.length > 0 ? this[sProperty] : this);
};
setTimeout(myArray.myMethod, 1000); // 未指定this,默认指向window
setTimeout(myArray.myMethod, 1500, "1"); // undefined
解决方案
将调用函数再包装一下
setTimeout(function () { myArray.myMethod(); // 根据上下文环境,this指向的是myArray }) setTimeout(function () { myArray.myMethod('2'); // 根据上下文环境,this指向的是myArray })
使用箭头函数,这种方式是比较常用的
setTimeout(() => { myArray.myMethod(); // 根据上下文环境,this指向的是myArray }) setTimeout(() => { myArray.myMethod('2'); // 根据上下文环境,this指向的是myArray })
还有一种方式,既然原生的
setTimeout
存在this指向,那么可以考虑重新封装一个setTimeout
,通过call
或者bind
来传入this。相当于是显示的指定this的值let myArray = ['zero', 'one', 'two']; myBoundMethod = (function (sProperty) { console.log(arguments.length > 0 ? this[sProperty] : this); }).bind(myArray); myBoundMethod(); // 'zero', 'one', 'two',this指向myArray myBoundMethod(1); // one setTimeout(myBoundMethod, 0); // 'zero', 'one', 'two' setTimeout(myBoundMethod,0, "1"); // 'one'
setInterval的this指向问题
在setInterval
调用时,也可能存在this指向问题。
myArray = ['zero', 'one', 'two'];
myArray.myMethod = function (sProperty) {
console.log(arguments.length > 0 ? this[sProperty] : this);
};
let id = setInterval(myArray.myMethod, 1000); // this指向window
解决方案
采用箭头函数
id = setInterval(() => myArray.myMethod(), 1000); // this指向myArray
使用
bind
的方式,显示指定thislet myArray = ['zero', 'one', 'two']; myBoundMethod = (function (sProperty) { console.log(arguments.length > 0 ? this[sProperty] : this); }).bind(myArray); id = setInterval(myBoundMethod, 1000); // this指向myArray
用途
合理的使用setTimeout
和setInterval
可以帮助我们解决很多实际问题。
倒计时
重点:不管是使用setTimeout还是使用setInterval实现倒计时,都可能存在误差。具体可以参考文章:setTimeout和setInterval实现倒计时的区别
注:实现倒计时10s
setTimeout
let seconds = 10;
(function getTime() {
if (seconds < 0) return;
setTimeout(function() {
console.log(seconds < 10 ? '0' + seconds + 's' : seconds + 's');
seconds--;
getTime();
}, 1000)
})();
运行结果:
setInterval
let seconds = 10;
setInterval(() => {
if (seconds < 0) return;
console.log(seconds < 10 ? '0' + seconds + 's' : seconds + 's');
seconds--;
}, 1000)
运行结果:
防抖
限制执行次数,多次密集的触发只会执行最后一次,无规律,更加关注结果
fuction debounce(fn, delay = 200) {
let timer = null;
return function() {
if (timer) clearTimeout();
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
节流
限制执行频率,有节奏的执行,有规律,,更加关注过程
function throttle(fn, delay = 100) {
let timer = null;
return function() {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
总结
- 本文介绍了setTimeout和setInterval的使用方法和注意事项,并且需要注意其this指向问题
- 解决this指向问题,一般采用箭头函数即可,但是不是所有场合都适合使用箭头函数