js实现0ms延时定时器的几种方式
这两天看到一篇介绍《如何实现准时的 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延时定时器的几种方式的更多相关文章
- iOS:延时执行的三种方式
延时执行的三种方式:performSelectorXXX方法.GCD中延时函数.创建定时器 第一种方式:NSObject分类当中的方法,延迟一段时间调用某一个方法 @interface NSObj ...
- js关闭当前页面(窗口)的几种方式总结(转)
js关闭当前页面(窗口)的几种方式总结 1. 不带任何提示关闭窗口的js代码 代码如下 <a href="javascript:window.opener=null;windo ...
- js实现页面跳转的两种方式
CreateTime--2017年8月24日08:13:52Author:Marydon js实现页面跳转的两种方式 方式一: window.location.href = url 说明:我们常用 ...
- js页面跳转常用的几种方式(转)
js页面跳转常用的几种方式 转载 2010-11-25 作者: 我要评论 js实现页面跳转的几种方式,需要的朋友可以参考下. 第一种: 复制代码代码如下: <script langu ...
- JS与JQ绑定事件的几种方式.
JS与JQ绑定事件的几种方式 JS绑定事件的三种方式 直接在DOM中进行绑定 <button onclick="alert('success')" type="bu ...
- node.js 下依赖Express 实现post 4种方式提交参数
上面这个图好有意思啊,哈哈, v8威武啊.... 在2014年的最后一天和大家分享关于node.js 如何提交4种格式的post数据. 上上一篇说到了关于http协议里定义的4种常见数据的post方法 ...
- java 定时器的三种方式
原地址:http://blog.csdn.net/haorengoodman/article/details/23281343/ /** * 普通thread * 这是最常见的,创建一个thread, ...
- js中面向对象(创建对象的几种方式)
1.面向对象编程(OOP)的特点: 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有的对象下继承出新的对象 多态:多对象的不同形态 注:本文引用于 http://www.cnblogs. ...
- python开发--Python实现延时操作的几种方式
1. time.sleep 2. sched.scheduler 3. threading.Timer 4. 借助其他程序 celery redis延时队列 在日常的开发中,往往会遇到这样的需求,需要 ...
随机推荐
- 为什么edge AI是一个无需大脑的人
为什么edge AI是一个无需大脑的人 Why edge AI is a no-brainer 德勤预计,到2020年,将售出超过7.5亿个edge AI芯片,即在设备上而不是在远程数据中心执行或加速 ...
- 【Python报错】RuntimeError: DataLoader worker (pid(s) 9764, 15128) exited unexpectedly
batch_size = 2# 256 def get_dataloader_workers(): #@save """使用4个进程来读取数据.""& ...
- GoLang:通过url将值从view层(.tpl)传递到controller层
beego框架 1.定义路由: beego.Router("/UpdateState/:statename/:id", &controllers.ContentContro ...
- anaconda安装VSCODE后,python报错
重新用anaconda时遇到了一点问题. 测试anaconda捆绑安装的VSCODE时写了一行print(1),然后报错. 后来发现用anaconda下载vscdoe时并不会给python一个路径,这 ...
- PTA题目集4-6总结
PTA题目集4-6总结 一:前言 在题集4-6中,所考查的主要知识点有正则表达式,类与类之间的调用,类的聚合,继承,封装,接口与多态,三种排序方法如选择排序,冒泡排序,插入排序,ArrayList,s ...
- StackOverflow经典问题:代码中如何去掉烦人的“!=null"判空语句
问题 为了避免空指针调用,我们经常会看到这样的语句 if (someobject != null) { someobject.doCalc();} 最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避 ...
- EasyExcel随笔
EasyExcel 注意点 不支持的功能 单个文件的并发写入.读取 读取图片 宏 csv读取 出现 NoSuchMethodException, ClassNotFoundException, NoC ...
- Java知识复习(四)
最近准备跳槽,又要好好复习基本知识了.过了个年,前面刚接触的springboot也只能先放放了.就先把自己复习了哪些罗列出来吧. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用== ...
- 乘风破浪,.Net Core遇见Dapr,为云原生而生的分布式应用运行时
Dapr是一个由微软主导的云原生开源项目,国内云计算巨头阿里云也积极参与其中,2019年10月首次发布,到今年2月正式发布V1.0版本.在不到一年半的时间内,github star数达到了1.2万,超 ...
- upload-labs通关记录
upload-labs通关记录 一句话木马解读 一般的解题步骤 或者可以直接用字典爆破一下 https://github.com/TheKingOfDuck/fuzzDicts/blob/master ...