LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

你不知道的setTimeout

liguoquan
2024年12月27日 12:39 本文热度 117
:你不知道的setTimeout


你不知道的setTimeout

前言

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而是发送到队列的前面。这样,既可以让步以提高网站上的输入响应速度,又可以确保在让步完成的工作不会延迟。

参考文档

总结

  1. setTimeout 可以实现延迟的一些事件,比如多少秒后返回首页等
  2. setTimeout 可以通过一定时间内事件只执行一次实现防抖
  3. setTimeout 可以替代 setInterval实现更好的轮询
  4. setTimeout 可以通过js事件的异步机制,优先展示用户关注的内容
  5. setTimeout 的事件定时器在某些场景下定时器不准,大部分场景下是准的,如果要求比较精确需要可以进行时间差的修正
  6. setTimeout 在熄屏或者非激活tab的场景下,计时器会延长变为1min执行一次
  7. setTimeout 是把事件插入到尾部,未来我们可以使用 scheduler.yield的暂停页面的操作,提升用户体验 、

该文章在 2024/12/27 12:39:50 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved