这两天看到一篇介绍《如何实现准时的 setTimeout?》的文章,文章起源于一道面试题:有什么办法让setTimeout准时呀?具体文章内容可查看附录【1】,看完之后,引起了我对setTimeout这个函数的探究兴趣,因此在MDN上重新查阅了相关文档,其中提到【最小延时 >=4ms】的一点,因此使用setTimeout不能实现0ms延时的定时器,如果要实现的话,提供了一个参考链接【2】,作者的实现思路是通过postMessage来模拟,绕过setTimeout的限制,从而实现0ms延时的定时器,说简单来讲就是起了一个宏任务去执行回调,先具体看下是怎么实现的:

(function() {
var timeouts = [];
var messageName = "zero-timeout-message";
// Like setTimeout, but only takes a function argument. There's
// no time argument (always zero) and no arguments (you have to use a closure)
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, "*");
}
function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
} window.addEventListener("message", handleMessage, true); // Add the one thing we want added to the window object.
window.setZeroTimeout = setZeroTimeout;
})();

作者还提供了一个demo页面【3】,通过于setTimeout(0)进行对比,在我浏览器的执行结果如下:

100 iterations of setZeroTimeout took 15 milliseconds.
100 iterations of setTimeout(0) took 488 milliseconds.

根据结果对比来看,setZeroTimeout执行比setTimeout快了上百倍,这是一个巨大的提升。今天想讨论的是除了上述这种方式,还可以通过哪些方式来实现一个0ms延时的定时器呢,首先,我们要确定一下我们自定义的定时器是异步的,其次是尽可能早的被执行。说起异步,js提供了好几种解决方案,我们可以逐一去验证。

在深入讨论各种实现方式之前,约定提供的setTimeout对比版本如下,后面自定义实现的方案都将和setTimeout版本的执行时间进行对比,代码比较简单:

(function() {
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setTimeout(test);
} else {
console.log('setTimeout执行时间:', Date.now() - start);
}
}
setTimeout(test);
})();

queueMicrotask

queueMicrotask这个api可以添加一个微任务,使用比较简单,直接传递一个回调函数即可,具体实现如下:

(function() {
function setZeroTimeout(fn) {
queueMicrotask(fn);
}
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setZeroTimeout(test);
} else {
console.log('setZeroTimeout执行时间:', Date.now() - start);
}
}
setZeroTimeout(test);
})();

通过和setTimeout版本进行对比,最终结果如下:

setZeroTimeout执行时间: 2
setTimeout执行时间: 490

关于这个API的介绍在MDN上有详细的说明,就不展开介绍了,这里多说一点,根据规范文档的说明,大多数情况下,推荐使用requestAnimationFrame()和requestIdleCallback()等api,因为queueMicrotask会阻塞渲染,在很多时候都不是一种好的实践。

async/await

async/await对于前端开发人员来说已经是必不可少的了,这里我们也可以用来实现:

(function() {
async function setAsyncTimeout(fn) {
Promise.resolve().then(fn);
}
let i = 0;
const start = Date.now();
async function test() {
if (i++ < 100) {
await setAsyncTimeout(test);
} else {
console.log('setAsyncTimeout执行时间:', Date.now() - start);
}
}
setAsyncTimeout(test);
})();

通过和setTimeout版本进行对比,最终结果如下:

setAsyncTimeout执行时间: 2
setTimeout执行时间: 490

如果不嫌麻烦,还可以通过Promise来实现,其实都是大同小异,无非多些点代码,这里就省略了。

MessageChannel

MessageChannel允许我们创建一个新的消息通道,并通过它的两个MessagePort属性发送数据,MessageChannel提供端口的概念,实现端口之间的通信,比如worker/iframe之间的通信。

(function() {
const channel = new MessageChannel();
function setMessageChannelTimeout(fn) {
channel.port2.postMessage(null);
}
channel.port1.onmessage = function() {
test();
};
let i = 0;
const start = Date.now();
function test() {
if(i++ < 100) {
setMessageChannelTimeout(test);
} else {
console.log('setMessageChannelTimeout执行时间:', Date.now() - start);
}
}
setMessageChannelTimeout(test);
})();

通过和setTimeout版本进行对比,最终结果如下:

setMessageChannelTimeout执行时间: 4
setTimeout执行时间: 490

第三种方式运行时间比前面两种更长些,因为通过MessageChannel产生的是宏任务,其他两种是微任务,微任务执行靠前,且会阻塞主线程,因此时间会长一点。

最后

本文提供了三种实现方式,都是围绕js提供异步解决方案来实现的,实现本身并不复杂,如果读者有其他实现方式,欢迎留言交流。

附录

​​【1】https://mp.weixin.qq.com/s/QRIXBoKr2dMgLob3Atq9-g

【2】https://dbaron.org/log/20100309-faster-timeouts

【3】https://dbaron.org/mozilla/zero-timeout

福禄·研发中心
福袋

js实现0ms延时定时器的几种方式的更多相关文章

  1. iOS:延时执行的三种方式

    延时执行的三种方式:performSelectorXXX方法.GCD中延时函数.创建定时器   第一种方式:NSObject分类当中的方法,延迟一段时间调用某一个方法 @interface NSObj ...

  2. js关闭当前页面(窗口)的几种方式总结(转)

    js关闭当前页面(窗口)的几种方式总结     1. 不带任何提示关闭窗口的js代码 代码如下 <a href="javascript:window.opener=null;windo ...

  3. js实现页面跳转的两种方式

      CreateTime--2017年8月24日08:13:52Author:Marydon js实现页面跳转的两种方式 方式一: window.location.href = url 说明:我们常用 ...

  4. js页面跳转常用的几种方式(转)

    js页面跳转常用的几种方式 转载  2010-11-25   作者:    我要评论 js实现页面跳转的几种方式,需要的朋友可以参考下. 第一种: 复制代码代码如下: <script langu ...

  5. JS与JQ绑定事件的几种方式.

    JS与JQ绑定事件的几种方式 JS绑定事件的三种方式 直接在DOM中进行绑定 <button onclick="alert('success')" type="bu ...

  6. node.js 下依赖Express 实现post 4种方式提交参数

    上面这个图好有意思啊,哈哈, v8威武啊.... 在2014年的最后一天和大家分享关于node.js 如何提交4种格式的post数据. 上上一篇说到了关于http协议里定义的4种常见数据的post方法 ...

  7. java 定时器的三种方式

    原地址:http://blog.csdn.net/haorengoodman/article/details/23281343/ /** * 普通thread * 这是最常见的,创建一个thread, ...

  8. js中面向对象(创建对象的几种方式)

    1.面向对象编程(OOP)的特点: 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有的对象下继承出新的对象 多态:多对象的不同形态 注:本文引用于 http://www.cnblogs. ...

  9. python开发--Python实现延时操作的几种方式

    1. time.sleep 2. sched.scheduler 3. threading.Timer 4. 借助其他程序 celery redis延时队列 在日常的开发中,往往会遇到这样的需求,需要 ...

随机推荐

  1. 编译原理-一种词法分析器LEX原理

    1.将所有单词的正规集用正规式描述 2.用正规式到NFA的转换算 得到识别所有单词用NFA 3.用NFA到DFA的转换算法 得到识别所有单词用DFA 4.将DFA的状态转换函数表示成二维数组 并与DF ...

  2. fiddler选项卡-AutoResponder(自动响应)

    AutoResponder 它可以把本来服务器响应的内容,使用本地内容来响应.这个对网站开发人员来说很有用. 1.AutoResponder界面 2.界面功能解析,根据上图标注位置 1.Enable ...

  3. java后端知识点梳理——MySQL

    MySQL的索引 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息,就像一本书的目录一样,可以加快查询速度.InnoDB 存储引擎的索引模型底层实现数据结构为 ...

  4. Waymo object detect 2D解决方案论文拓展

    FixMatch 半监督中的基础论文,自监督和模型一致性的代表作. Consistency regularization: 无监督学习的方式,数据\(A\)和经过数据增强的\(A\)计做\(A'\) ...

  5. 注册中心ZooKeeper,Eureka,Consul,Nacos对比

    简介 ​ 服务注册中心本质上是为了解耦服务提供者和服务消费者.对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的.更进一步,为了支持弹性扩缩容特性,一个微服务的提供者 ...

  6. 源码分析Gateway请求转发

    本期我们主要还是讲解一下Gateway,上一期我们讲解了一下Gateway中进行路由转发的关键角色,过滤器和断言是如何被加载的,上期链接: https://www.cnblogs.com/guoxia ...

  7. 玩转STM32MP157-开发环境搭建

    (一)STM32MP 1.什么是 STM32MPU STM32MPU是 ST 推出的 Cortex-A7 + Cortex-M4 多核异构处理器 STM32MPU151 是单核 A7+M4,.STM3 ...

  8. linux 下安装 docker 环境

    一分钟了解 Docker Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源.Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然 ...

  9. 《面试补习》- Java集合知识梳理

    一.ArrayList ArrayList 底层数据结构为 动态数组 ,所以我们可以将之称为数组队列. ArrayList 的依赖关系: public class ArrayList<E> ...

  10. GCP消息队列Pubsub详解,简单好用还不用自己运维

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 GCP的Pubsub是一种异步消息传递服务,可将生产事件的服务与处理事件的服务隔离开.消息队列的作用就不多作介绍 ...