别再用错setTimeout和setInterval了

简介: `setTimeout`和`setInterval`都可以用于设置定时器,参数相似,但是使用效果和用途截然不同。本文就重点阐述两者之间的区别

前言

setTimeoutsetInterval都可以用于设置定时器,参数相似,但是使用效果和用途截然不同。本文就重点阐述两者之间的区别

使用方法

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

解决方案

  1. 将调用函数再包装一下

    setTimeout(function () {
        myArray.myMethod(); // 根据上下文环境,this指向的是myArray
    })
    setTimeout(function () {
        myArray.myMethod('2'); // 根据上下文环境,this指向的是myArray
    })
  2. 使用箭头函数,这种方式是比较常用的

    setTimeout(() => {
        myArray.myMethod(); // 根据上下文环境,this指向的是myArray
    })
    setTimeout(() => {
        myArray.myMethod('2'); // 根据上下文环境,this指向的是myArray
    })
  3. 还有一种方式,既然原生的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

解决方案

  1. 采用箭头函数

    id = setInterval(() => myArray.myMethod(), 1000); // this指向myArray
  2. 使用bind的方式,显示指定this

    let myArray = ['zero', 'one', 'two'];
    myBoundMethod = (function (sProperty) {
        console.log(arguments.length > 0 ? this[sProperty] : this);
    }).bind(myArray);
    id = setInterval(myBoundMethod, 1000); // this指向myArray

用途

合理的使用setTimeoutsetInterval可以帮助我们解决很多实际问题。

倒计时

重点:不管是使用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指向问题,一般采用箭头函数即可,但是不是所有场合都适合使用箭头函数
相关文章
|
1月前
|
JavaScript 调度
setTimeout
【10月更文挑战第22天】
10 1
|
3月前
使用requestAnimationFrame模拟实现setTimeout和setInterval
`rafTimeout` 函数采用 `requestAnimationFrame` 实现延时或周期性调用,提供与 `setTimeout` 和 `setInterval` 类似的功能。接受参数包括要执行的函数 `fn`、延迟时间 `delay`(默认 0ms)及是否周期执行 `interval`(默认为单次执行)。返回值为包含 `id` 的对象,可用于取消定时器。通过 `cancelRaf` 或 `cancelAnimationFrame` 方法可取消对应的 `rafTimeout` 定时器。
138 2
使用requestAnimationFrame模拟实现setTimeout和setInterval
|
消息中间件 JavaScript 前端开发
setTimeout和setInterval
setTimeout和setInterval
|
JavaScript 前端开发 Java
|
JavaScript 前端开发
|
JavaScript 前端开发
第46天:setInterval与setTimeout的区别
js的setTimeout方法用处比较多,通常用在页面刷新了、延迟执行了等等。今天对js的setTimeout方法做一个系统地总结。 setInterval与setTimeout的区别 说道setTimeout,很容易就会想到setInterval,因为这两个用法差不多,但是又有区别,今天一起总结了吧! 1、setTimeout 定义和用法: setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式。
1496 0