防止因定时器遗留而引发的 JavaScript 应用故障:巧用最优实践
2023-11-10 07:38:55
对于我们工程师来说,如果没有JavaScript,我们几乎寸步难行,它已是整个软件行业的基石,我们几乎每天都在编写JavaScript代码。许多人经常会碰到这样一些错误,这些错误大多源于我们忘记清除setTimeout或setInterval生成的定时器。
当组件挂载后,每过一秒,sec 的值就会+1,看起来没有问题,但组件销毁时,setI…
setTimout与setInterval的优缺点分析
对于一个定时器而言,需要考虑的问题是其执行次数 和执行时机 。
执行次数 可以分为只执行一次 和重复执行 两种情况。
执行时机 可以分为指定时间执行 和指定时间间隔内重复执行 两种情况。
setInterval
setInterval(callback, 1000) //每隔1秒调用一次回调函数callback
setInterval可以根据设定的时间间隔重复执行回调函数callback,但无法设置回调函数只执行一次。
setTimeout
setTimeout(callback, 1000) //延迟1秒调用回调函数callback
setTimeout可以根据设定延迟时间只调用回调函数callback一次,但无法设定回调函数重复执行。
setTimout与setInterval使用中的注意事项
由上面的例子可知,选择使用setTimeout或setInterval应根据具体的业务场景需求。它们的使用都会带来好处,但同样也会带来隐患,下面我们就来分析一下使用setTimeout和setInterval要注意的问题。
setInterval
setInterval的回调函数是循环执行的,如果定时器的回调函数中没有对循环次数做出限制,则会一直执行下去,直到用户主动关闭网页或JavaScript代码本身因运行出错而停止,显然这并不是我们期望的结果。
通常,setTimeout函数的使用场景较为单一,就是在指定时间后触发一次事件。setInterval的用途则广泛得多,从简单的轮询,到复杂UI效果,甚至涉及流程控制和表单验证等场景,都会用到setInterval函数。
所以,我们可以得出如下结论:setTimeout的使用场景相对简单,而setInterval的使用场景更为复杂和多样。
setTimeout
setTimeout的回调函数只执行一次,且执行时间是固定的。显然,setTimeout不会像setInterval那样因为反复触发回调函数而导致资源浪费和性能问题。因此,在以下场景中,我们可以考虑使用setTimeout而不是setInterval:
- 当我们要执行一个需要在指定时间后只执行一次的操作时。
- 当我们要执行一个需要在指定时间后重复执行多次的操作,但可以控制重复执行的次数时。
虽然从表面上看,setTimeout的使用场景似乎更加受限,但实际场景中,满足上述要求的场景其实并不罕见。比如,以下场景就完全可以考虑使用setTimeout:
- 页面加载完成后,只执行一次页面的初始化操作。
- 表单提交时,只执行一次表单验证操作。
- 滚动条拉动到底部时,只执行一次加载更多数据的操作。
- 在一个模态框中,点击确定按钮时,只执行一次关闭模态框的操作。
- 在一个定时任务系统中,只执行一次定时任务的操作。
如何强制实行 setTimeout 与 setInterval 的最佳实践
为了避免定时器遗留而引发的故障,我们需要遵循一些最佳实践:
- 在组件销毁时清除定时器。
class MyClass {
constructor() {
this.timer = setTimeout(() => {
// Do something
}, 1000)
}
componentWillUnmount() {
clearTimeout(this.timer)
}
}
- 使用 WeakMap 存储定时器。
WeakMap是一种弱引用集合,它可以将键值对存储在内存中,但不会阻止键值对被垃圾回收。这样,当组件被销毁时,WeakMap中的定时器也会被垃圾回收,从而避免了内存泄漏。
const timers = new WeakMap()
function setTimeout(callback, delay) {
const timer = setTimeout(() => {
callback()
timers.delete(timer)
}, delay)
timers.set(timer, callback)
return timer
}
function clearTimeout(timer) {
timers.delete(timer)
clearTimeout(timer)
}
- 使用箭头函数创建定时器。
箭头函数不绑定自己的 this
值,因此在组件销毁时,箭头函数中的 this
值仍然指向组件实例,从而可以正确地清除定时器。
this.timer = setTimeout(() => {
this.doSomething()
}, 1000)
通过遵循这些最佳实践,我们可以避免定时器遗留而引发的故障,从而确保应用程序的代码健壮性和性能得到优化。