返回

Javascript: 理解setTimeout中 clearTimeout 使用技巧

javascript

Javascript 函数中使用未来定义的参数

Javascript中经常会用到 setTimeout 函数实现延迟执行的效果,如果延迟期间需要取消并重新计时,clearTimeout 就是必需的工具。这里我们探讨一种看似反直觉的情况:在函数内部,clearTimeout 如何引用一个稍后才会赋值的参数 timeoutID

问题剖析

让我们仔细看看这段代码:

let timeoutID;

function updateMessage() {
  const message = document.querySelector('.js-message');

  message.innerHTML = 'Added';

  clearTimeout(timeoutID);

  timeoutID = setTimeout(function() {
    message.innerHTML = '';
  }, 2000);

}

表面上看,clearTimeout(timeoutID) 似乎在使用一个尚未定义的变量 timeoutID。在 clearTimeout 调用的下一行,timeoutID 才被赋予了 setTimeout 返回的值。这里发生的是 Javascript 中变量作用域和赋值时机的巧妙结合。

作用域和声明提升

首先,let timeoutID; 语句在全局作用域中声明了一个变量 timeoutID, 但没有给它赋值。关键点是使用 let 声明的变量,在代码块(这里是全局作用域)开始直到执行到变量声明的那行,存在所谓的 “暂时性死区”。 在这里,timeoutID 被声明了,这意味着即使在函数内部访问该变量,也不算“未定义”,Javascript 引擎知道存在这样一个变量,并且能通过该变量找到相应的值。初始值为 undefined 。

函数执行顺序

接下来我们分析代码的执行顺序。每次点击按钮都会调用 updateMessage 函数:

  1. 更新消息: message.innerHTML = 'Added'; 更新页面消息。
  2. 取消定时器(尝试): clearTimeout(timeoutID) 会被调用, 由于此时timeoutID 的值为 undefined, clearTimeout(undefined) 并不会报错,并且相当于没执行。如果之前存在未被清除的定时器,那么本次调用清除的也会是对应的定时器(如果timeoutID的值已经不是undefined的情况)。
  3. 设置新定时器: timeoutID = setTimeout(...); 会启动一个新的定时器,setTimeout 返回的值(一个唯一标识符,代表该定时器),随后赋值给变量 timeoutID

这也就意味着在点击按钮第一次时 clearTimeout(undefined) 不会有任何作用,但是会在timeoutID = setTimeout(...)这一步赋值。 当点击第二次按钮的时候,前一个按钮创建的计时器的id存在 timeoutID 里了, clearTimeout(timeoutID) 可以取消上一个计时器。并且会在此次点击按钮后开启新的计时器,再次把当前新的计时器ID保存进timeoutID 里。

解决方案及原理

理解的关键在于变量的声明和赋值过程,以及 JavaScript 引擎如何处理未初始化变量和函数调用顺序。 代码的关键逻辑利用了 timeoutID 的值可以被更新,以及 clearTimeout(undefined) 并不会导致错误这一点。

总结

这种机制并不是在 “未来” 获取参数。它仅仅是利用了 Javascript 中变量的“声明提升” 和 setTimeout 的返回值会被赋值给 timeoutID 这两点。每次函数调用,都会尝试清理旧定时器,无论之前是否设置过定时器,最终会设置一个新的定时器, 并将id保存。这种方法在很多场景中都非常有用。例如:输入框的搜索自动补全,防止用户频繁输入造成的重复查询请求;按钮的点击事件,避免多次快速点击产生多次请求等场景。

安全建议: 虽然 clearTimeout(undefined) 不会报错,但出于代码清晰度的考虑,可以在clearTimeout前判断下 timeoutID 是否为真值,例如使用:
if (timeoutID) {clearTimeout(timeoutID)}

这个方式的效率不会差于 clearTimeout(timeoutID) ,也能让代码更易读。

操作步骤:

  1. 将上面的HTML代码复制粘贴到一个HTML文件中
  2. 将Javascript代码复制粘贴到html文件里的 <script></script> 标签中。
  3. 在浏览器中打开该HTML文件,点击 "Add to cart" 按钮进行测试。
  4. 观察消息 “Added” 出现然后消失的时间间隔,以及多次点击按钮后定时器的行为。

这个例子展示了Javascript中时间延迟函数的高级用法。深入理解背后的机制有助于我们编写更高效,可靠的Javascript代码。