前言
setTimeout()
我们在日常工作中经常使用,最近做了一个功能是关于setTimeout()
的,总结了一些用法。
在这篇文章中,你将了解 setTimeout()
方法——它是什么以及如何在你的程序中使用它。
以下是我在这篇快速指南中要介绍的内容:
- JavaScript 中的
setTimeout()
基本语法 - 进阶语法:防抖、代码逻辑执行时间可能比定时器时间间隔要长如何处理
setTimeout()
的定时器是否精准- 定时器在非激活tab或者熄屏的时候还会按照预期去执行吗
- 定时器如何进行时间纠正
- 简单比较下scheduler.yield
基本的语法
scss
代码解读
复制代码setTimeout(code)
setTimeout(code, delay)
setTimeout(functionRef)
setTimeout(functionRef, delay)
setTimeout(functionRef, delay, param1)
setTimeout(functionRef, delay, param1, param2)
setTimeout(functionRef, delay, param1, param2, /* …, */ paramN)
例如我们写个最简单的
我们来添加一个参数进行测试 如下,可以打印出来获取到的参数params1和params2
返回值
返回值 timeoutID
是一个正整数,表示由 setTimeout()
调用创建的定时器的标识符。可以将这个值传递给 clearTimeout()
来取消该定时器。
我们尝试在1秒的时候对timerId进行清除,测试下是否可以正常打印,如你所料,不会打印timerId内的数据
进阶用法
1 防抖(常用于表单提交)
我们在表单提交的时候,比如希望表单的提交按钮在1秒内之内只生效一次,可以利用settimeout来实现。
如下,假如在页面上有一个id为submitBtn的按钮,添加了一个点击事件,当1秒内每次点击都会清除之前的timeoutId,不会执行提交的逻辑。从而确保只有在最后一次点击后的1秒内没有再次点击时,才会执行实际的操作。
javascript
代码解读
复制代码 let submitBtn = document.getElementById("submitBtn");
let timeoutId;
function onTest() {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
// 在这里编写提交逻辑
alert("按钮被点击了!");
}, 1000);
}
submitBtn.addEventListener("click", onTest);
2 优先展示用户希望看到的内容(利用JS事件机制)
假如我们有一个人员的新增表单,部门树的下拉框有10K条数据,假如我们还没有虚拟下拉树,数据的渲染会很慢, 打开这个新增的表单可能会有3到5S的延迟后表单才会打开。
我们可以将这个部门树的下拉值绑定写在了settimeout内,等主线程的任务执行后,再执行绑定部门树的操作,可以优先将其他的内容展示出来后再进行部门树的绑定。
3 替代setInterval防止请求阻塞
比如我们有个需求: 1秒的间隔轮询服务器,页面展示内容
正常的话可以用setInterval没什么问题,
假如请求的接口可能因网络延迟、服务器无响应以及许多其他的问题而导致请求无法在分配的时间内完成(假如服务器处理了3秒)
我们把请求的间隔设置为1秒,实际的接口在2秒多,就会造成请求的阻塞,其实我们想实现的是在一次接口请求结束后再发起下一次请求
如下方的截图
接口固定在大约2秒返回,就会导致永远有几个请求在阻塞,跟我们的预想不同
我们对上面的代码进行改造如下
实现如果如下,我们保证有一个请求在pending,会在一个请求发出后,再进行发送下一次请求
代码解释如下
在上面的代码片段中,声明了一个具名函数 loop()
,并被立即执行。loop()
在完成代码逻辑的执行后,会在内部递归调用 setTimeout()
。虽然该模式不保证以固定的时间间隔执行,但它保证了上一次定时任务在递归前已经完成。
可能遇到的场景
1 settimeout这个定时器准吗
答案:正常场景下是准的,某些场景下是不准的,
有很多因素会导致超时比设定的预期值更久 如需查看更多的原因点击我
嵌套多次的时候会有4ms的延迟(可以点击下方的例子进行测试)
当嵌套多次的结果如下,就不做详细介绍了,了解就行。目前我没想到哪些场景下settimeout会被嵌套调用5次,就不做继续研究,我们只需要知道这个概念就行,等出现这个问题了有个排查方向就行
主线程有耗时的任务
我们做个实验,我们对id为main对dom进行2万次的修改innerHtml,在控制台打印时间,
原本2秒变为了4秒,所以settimeout 有时候并不会按照预期的时间间隔来执行
这里其实就涉及到了js的事件为单线程机制,我们用performance简单分析下,看到有个3865ms的parse HTML的,然后执行Run Microtasks也就是微任务,也就是等我们的同步任务执行后,再去执行settimeout内的东西
在非活动标签的tab,待机下的settimeout还会按照预期执行吗?
以下的测试是在chrome的mac版本进行测试
代码很简单,写了个2秒的定时器
待机状态
tab非激活
过几分钟后,定时器会从2秒变为1分钟
由此我们得知,在浏览器激活的时候,settimeout 会按照我们的预期去执行,在非激活(tab不选中过段时间、电脑处于待机状态)下,定时器会按照1min一次去执行,
但是有种特殊场景,audio假如正在播放,此时页面的settimeout会被当作激活状态
settimeout 时间纠正
当然,因为js的事件机制,settimeout存在时间偏差,就会存在时间纠正,下面介绍了两个js的时间纠正方式,虽然我试用下来不太理想,也可能是我的姿势不对,有更好的方式欢迎和我讨论
计算时间差(并不能完全避免,只能纠正)
直接在网上抄写了个例子。 使用 setTimeout 进行计时,每次计时都会用系统时间修复时间差
js
代码解读
复制代码 function timer_setTimeout() {
const speed = 1000; // 设置定时器的间隔速度为1000毫秒(1秒)
let countTime = 0; // 初始化计时器计数变量
let start = new Date().getTime(); // 记录计时开始时的时间戳
// 定义计时器的执行函数
function run() {
countTime++; // 每次执行时递增计时器的计数
// 计算按照计时器当前速度实际经过的时间(countTime * 速度)
let realTime = (countTime * speed);
// 计算从计时开始到现在系统经过的时间
let sysTime = (Date.now() - start);
// 计算实际时间和理想时间之间的差异
let patch = (sysTime - realTime);
// 使用系统时间进行修复,调整下一次setTimeout的延迟时间
// 通过设置speed - diff,尝试校正setTimeout的延迟,以补偿偏差
window.setTimeout(run, (speed - patch));
// 更新页面元素timeoutDom的文本内容,显示当前计时器的值
timeoutDom.innerText = `setTimeout: ${countTime}`;
}
// 启动计时器,初始调用run函数,并设置延迟为speed
window.setTimeout(run, speed);
}
// 调用函数,创建并启动setTimeout计时器
timer_setTimeout();
webworker(测试下来效果不好)
javascript
代码解读
复制代码 console.log(new Date());
for (let index = 0; index < 20000; index++) {
document.getElementById('main').innerHTML += index;
}
function timer_worker() {
// 创建一个Blob对象,用于生成一个可以在Web Worker中执行的JavaScript代码URL
const blob = new Blob(
[
`let countTime = 0;
self.setInterval(() => { // 在Web Worker的上下文中设置一个定时器
countTime++; // 每次定时器触发时递增计数器
self.postMessage(countTime); // 使用postMessage方法发送计数器的值到主线程
}, 5000);` // 定时器的时间间隔设置为1秒
],
{ type: 'application/javascript' }
); // 指定Blob的内容类型为JavaScript
// 使用URL.createObjectURL方法创建一个可以被Web Worker使用的URL
const worker = new Worker(URL.createObjectURL(blob));
// 设置Web Worker的onmessage事件处理器
// 当Web Worker使用postMessage发送消息时,该处理器会被触发
worker.onmessage = (ev) => {
// 更新DOM元素workerDom的文本内容,显示从Web Worker接收到的计时器值
console.log(new Date() + ` worker: ${ev.data}`);
};
}
timer_worker();
遇到长的任务的时候,还是会延迟执行
scheduler.yield
在查找 setTimeout
的资料的时候,发现了有一个比较好的东西,scheduler.yield
, 感兴趣的老板可以点击我进行体验,目前兼容性不好(24年8月21日的才支持),不做详细介绍。
兼容性如下
总结一句话
使用 setTimeout
是将事件插入在队列的结尾。yield而是发送到队列的前面。这样,既可以让步以提高网站上的输入响应速度,又可以确保在让步后完成的工作不会延迟。
参考文档
- developer.chrome.com/blog/introd…
- developer.chrome.com/blog/timer-…
- developer.mozilla.org/zh-CN/docs/…
总结
setTimeout
可以实现延迟的一些事件,比如多少秒后返回首页等setTimeout
可以通过一定时间内事件只执行一次实现防抖setTimeout
可以替代setInterval
实现更好的轮询setTimeout
可以通过js事件的异步机制,优先展示用户关注的内容setTimeout
的事件定时器在某些场景下定时器不准,大部分场景下是准的,如果要求比较精确需要可以进行时间差的修正setTimeout
在熄屏或者非激活tab的场景下,计时器会延长变为1min执行一次setTimeout
是把事件插入到尾部,未来我们可以使用scheduler.yield
的暂停页面的操作,提升用户体验 、