很多朋友对于为什么setTimeout的延迟最小为4ms?和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
正文
在前端技术圈里,经常有一个关于setTimeout的结论,“setTimeout的最小设置延迟为4ms”。按照“有点”的方法,在回答问题之前,你必须先“看看它是否正确”,然后“看看它是否正确或为什么”。
我们先看第一个问题,“是否有具体的规范规定4ms,或者只是行业惯例的既定事实?”
熟悉前端的人都知道,setTimeout不是由ECMAScript维护的,而是由宿主环境提供的。后面的具体规范由whatwg维护(至于为什么ECMAScript不直接提供setTimeout函数,在2011年的esdiscuss上有很多讨论,参与者包括Brendan Eich、Kyle Simpson等一群前辈,稍后会简单提及稍后或在另一篇文章中)。回到html标准,setTimeout()和setInterval()在8.6 Timers-2020/6/23中有详细描述。我们只看第10-13行:
如果超时小于0,则将超时设置为0。
如果嵌套级别大于5,且超时小于4,则将超时设置为4。
将嵌套级别增加一级。
设任务的定时器嵌套级别为嵌套级别。
从以上规格可以看出:
如果设置的超时时间小于0,则设置为0。如果嵌套层数超过5层且超时时间小于4ms,则设置超时时间为4ms。至此,我们似乎找到了4ms的来源,对setTimeout的最小延迟有了更精确的定义——“只有嵌套层数超过5层且超时小于4ms时才会设置4ms”。
有些人可能会好奇,“定时器嵌套级别是多少?” (我也很好奇),详细请看下面的代码(如何在浏览器源码中实现定时器嵌套级别和最小延迟,后面会通过chromium源码进行讲解):
setTimeout(()={setTimeout(()={setTimeout(()={setTimeout(()={setTimeout(()={},0)},0)},0)},0)},0)至此,setTimeout关于最小延迟的设计似乎就可以解释了。但正如B站常说的一句话,“你只看到二楼,以为我在一楼,其实我已经到了五楼”。
当我看到一群朋友提出这个问题时,我的第一个想法不仅是找到规范的来源,而且还有另外两个顾虑:
各大浏览器厂商是否按照规范实现了?如果没有,为什么? 4ms这个数字到底是如何确定的呢?正如我经常想的那样,“看似简单的设计结果背后一定有一个根源。这个根源可能来自于当时的痛点、背景和限制。我们不仅要理解设计,更重要的是理解什么?”是设计的背后。” - 例如,各大科技巨头和制造商如何在幕后竞争和权衡。”
我们先看一下各种主流浏览器对setTimeout延迟的各种边界条件的输出(需要注意的是,这里没有考虑由于单个事件循环的延迟而导致的setTimeout延迟的增加,而是简化为最原始的情况)具体来说,不考虑单循环执行时间太长,假设单循环执行时间小于ms级别):
Chrome 83.0.4103.106和Safari/edgeFirefox 65.0.1和IE 11。看上图我们会发现,对于0ms和1ms的setTimeout设置延迟,各大浏览器厂商都采取了两种策略。至于具体的策略,我们会在浏览器源码中找到(这里只展示chromium的源码,其他webkit或者Firefox可以自行下载查看)。在chromium的blink目录下,有一个叫DOMTimer的.cpp文件,网上地址,这也是设置定时器延迟的地方:
staticconstintmaxIntervalForUserGestureForwarding=1000;//OnesecondmatchesGecko.staticconstintmaxTimerNestingLevel=5;staticconstdoubleoneMillisecond=0.001;//Chromium 使用最小定时器间隔4ms。我们希望//降低;但是,有一些编码很差的网站//创建CPU 旋转循环。使用4ms 可以防止CPU 旋转太忙,并在CPU 旋转和最小可能间隔计时器之间提供平衡。staticconstdoubleminimumInterval=0.004;doubleintervalMilliseconds=std:max(oneMillisecond,interval*oneMillisecond);if(intervalMilliseconds=maxTimerNestingLevel)intervalMilliseconds=minimumInterval;代码逻辑很清晰,设置了三个常量:
maxTimerNestingLevel=5。即HTML标准中提到的嵌套级别minimumInterval=0.004。这是HTML标准中提到的最小延迟。在第二段代码中,我们将看到首先在延迟时间和1ms 之间取一个最大值。也就是说,当嵌套级别不满足时,最小延迟时间设置为1ms。这也解释了为什么在chrome中测试setTimeout有上面的结果。
在chromium的注释中,解释了为什么设置minimumInterval=4ms。简单来说,Chromium 团队想要设置更低的延迟时间(事实上,他们期望达到亚毫秒级别)。但由于一些网站(如纽约时报网站)对setTimeout定时器使用不佳,导致设置延迟过高。 Low会导致CPU-spinning(稍后我们会解释什么是CPU-spinning),因此chromium做了一些基准测试,选择4ms作为其minimumInterval。
我们先深入到操作系统层面。 Windows默认的定时器分辨率是10-15.6ms(这里可以理解为定时器的粒度)。也就是说,浏览器的定时器最初取决于操作系统级别的定时器。解决。切换到setTimeout,设置的最小延迟将至少为10ms。但在CPU性能方面,处理器速度从1995年的500HZ提升到了3GHZ以上(2010年达到),但是Windows的默认定时器没有改变,仍然保持原来的10-15.6ms(这里你会看到浏览器厂商和操作系统厂商的思考角度不同)。浏览器厂商(Chrome)认为默认定时器影响了网页的表达(10-15.6ms太长)。对于浏览器内部来说,如果时钟节拍很长,就意味着浏览器会休眠很长时间,这在某些方面会导致浏览器性能下降。
上面的解释告诉我们一个既定的事实,window下所有浏览器中定时器的实现最初取决于操作系统的定时器,为10-15.6ms。实际的测试结果可以从Erik Kay的可视化排序测试和John Resign(大名鼎鼎的JQuery的作者,你会发现大佬们对底层都有自己的理解)的快速测试方案中看到,具体是John Resign的文章2008年。
Chrome非常关心10-15.6ms的定时器。我们知道Chrome 的目标是成为一款高性能的现代浏览器。具体来说,在定时器分辨率方面,希望能够达到亚毫米级别(小于1ms)。因此,chrome团队希望改变浏览器对操作系统定时器的依赖。它在windows和linux/unix系统下采用不同的解决方案来实现其目标。 Linux/unix有专门的API来修改系统默认的定时器分辨率,但是在windows下有点麻烦。最终,Chromium 团队选择了与Flash 和Quicktime 相同的API 来替换系统默认的计时器分辨率。
修改OS默认定时器分辨率后,chrome的性能得到了很大的提升。具体到chrome 1.0 beta版本,计时器分辨率设置为1ms(已经接近团队的预期)。有人可能会问,既然我们追求低延迟,为什么不直接设置为0ms呢?
原因是,如果浏览器允许0ms,就会导致JavaScript引擎过度循环,这意味着如果浏览器架构是单进程的话,网站可能很容易变得无响应。因为浏览器本身也是建立在事件循环之上的,如果缓慢的JavaScript引擎不断地安排通过0ms定时器来唤醒系统,那么事件循环就会被阻塞。那么这个时候用户会面临什么呢?经历CPU 旋转和浏览器基本上同时挂起的情况想想就令人沮丧。如果一款浏览器经常让用户遇到这种情况,绝对没有人愿意使用它。毕竟很少有人愿意被虐待。这就是chrome 1.0 beta设置为1ms的原因。
结果看起来很不错,但随后有团队报告了Bug(具体有两个,一个是前面提到的纽约时报网站Bug,另一个是Intel团队发现的chrome功耗异常)。发现定时器导致CPU旋转,CPU旋转的后果是计算机无法进入睡眠模式(低功耗模式),这意味着电量消耗得非常快。因此,chrome团队必须解决现实问题(另外,由于当时chrome的市场份额没有今天这么大,所以不敢太大)。 chrome团队当时的计划是对定时器设置很多限制。后来chrome团队经过一些实验发现,将1ms增加到4ms似乎可以消除大多数机器上CPU旋转和功耗过高的问题。这种权衡实现了chrome 团队的目标,即在不产生更多问题的情况下提供更准确的计时器。
说句题外话,其实一开始,Chrome团队就与Windows团队进行了沟通,希望Windows能够提供动态调整硬件时钟节拍间隔的功能,以匹配上层应用的需求,但是结果沟通不太好。好的。这可以从微软曾经发表的一次关于为什么不愿意做出这样的改变的演讲中了解到。在演讲中,他们希望未来的操作系统能够对上层应用程序强制执行较低的唤醒率(100ms),以减少许多不当行为。程序。也就是说,chrome团队的期望和windows团队的期望是冲突的。至此,我们就会明白,不同的团队对于同一件事情有不同的考虑。
Chrome团队调整定时器后(性能提升不少),其他主流浏览器如(Safari、Opera、Firefox、IE)都采用了4ms的设置,不同的浏览器会在不同的情况下进行定时器节流。 (这就是为什么我们不同的浏览器测试一开始结果不同的原因)。后来HTML标准制定了相关规范。
事实上,定时器分辨率并不是一个经常讨论的话题(因为它需要大量的基础知识,而且往往比较低级),但事实上定时器分辨率一直在不断发展。
正如Nicholas C. Zakas 在他的一篇文章中提到的那样,“我们越来越接近对浏览器进行每毫秒控制的程度。当有人弄清楚如何在没有CPU 中断的情况下管理计时器时,我们很可能会看到计时器在那之前,请记住4 毫秒,但请记住,您仍然不会总是得到这个。”
总结
至此,你就可以明白setTimeout的4ms是如何设置的了。我们可以对该方法的最小延迟有更精确的定义。
不同浏览器的最小延迟会有所不同。例如chrome的最小延迟是1ms。如果定时器嵌套层数较多,则最小延迟为4ms。具体的嵌套级别阈值在不同的浏览器中是不同的。在HTML 标准中为5,在Chrome 中为=5。另外,我们还了解了前面提到的两个问题:
各大浏览器厂商是否按照规范实现了?如果没有,为什么? 4ms这个数字到底是如何确定的呢?各大浏览器厂商并没有完全按照规范来实现,因为他们各自有自己的基准,然后不同的浏览器厂商做了不同的设置。另外,HTML标准对此类影响不大的变量提供了相应的灵活更改。我们也了解4ms的背景以及浏览器厂商和操作系统厂商背后的不同考虑,以及各自的解决方案决策和权衡。
推荐JavaScript学习相关文章
《如何处理 Node.js 中出现的未捕获异常?》
《Angular v10.0.0 正式发布,不再支持 IE9/10》
《基于 Docker 的 SSR 持续开发集成环境实践》
《细聊图解webpack 指南手册》
《一文带你彻底搞懂 NPM 知识点「进阶篇」》
《细聊webpack性能优化面面观》
《JS实现各种日期操作方法汇总》
《「实践」细聊前端性能优化总结》
《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》
《「多图」一文带你彻底搞懂 Web Workers (上)》
《「多图」一文带你彻底搞懂 Web Workers (中)》
《深入细聊前端下载总结「干货」》
《细品西瓜播放器功能分析(上)「实践」》
《细品西瓜播放器功能分析(下)「实践」》
《细聊50道JavaScript基础面试题「附答案」》
《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》
《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》
《细聊前端架构师的视野》
《细聊应用场景再谈防抖和节流「进阶篇」》
《前端埋点统一接入方案实践》
《细聊微内核架构在前端的应用「干货」》
《一种高性能的Tree组件实现方案「干货」》
《进击的JAMStack》
《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》
《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》
《JavaScript常用API合集汇总「值得收藏」》
《推荐10个常用的图片处理小帮手(上)「值得收藏」》
《推荐10个常用的图片处理小帮手(下)「值得收藏」》
《JavaScript 中ES6代理的实际用例》
《12 个实用的前端开发技巧总结》
《一文带你搞懂搭建企业级的 npm 私有仓库》
《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》
《细说前端开发UI公共组件的新认识「实践」》
《细说DOM API中append和appendChild的三个不同点》
《细品淘系大佬讲前端新人如何上王者「干货」》
《一文带你彻底解决背景跟随弹窗滚动问题「干货」》
《推荐常用的5款代码比较工具「值得收藏」》
《Node.js实现将文字与图片合成技巧》
《爱奇艺云剪辑Web端的技术实现》
《我再也不敢说我会写前端 Button组件「实践」》
《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》
《Node Buffers 完整指南》
《推荐18个webpack精美插件「干货」》
《前端开发需要了解常用7种JavaScript设计模式》
《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》
《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》
《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》
《10个打开了我新世界大门的 WebAPI(上)「实践」》
《10个打开了我新世界大门的 WebAPI(中)「实践」》
《10个打开了我新世界大门的 WebAPI(下)「实践」》
《「图文」ESLint 在中大型团队的应用实践》
《Deno是代码的浏览器,你认同吗?》
《前端存储除了 localStorage 还有啥?》
《Javascript 多线程编程的前世今生》
《微前端方案 qiankun(实践及总结)》
《「图文」V8 垃圾回收原来这么简单?》
《Webpack 5模块联邦引发微前端的革命?》
《基于 Web 端的人脸识别身份验证「实践」》
《「前端进阶」高性能渲染十万条数据(时间分片)》
《「前端进阶」高性能渲染十万条数据(虚拟列表)》
《图解 Promise 实现原理(一):基础实现》
《图解 Promise 实现原理(二):Promise 链式调用》
《图解 Promise 实现原理(三):Promise 原型方法实现》
《图解 Promise 实现原理(四):Promise 静态方法实现》
《实践教你从零构建前端 Lint 工作流「干货」》
《高性能多级多选级联组件开发「JS篇」》
《深入浅出讲解Node.js CLI 工具最佳实战》
《延迟加载图像以提高Web网站性能的五种方法「实践」》
《比较 JavaScript 对象的四种方式「实践」》
《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》
《前端如何一次性处理10万条数据「进阶篇」》
《推荐三款正则可视化工具「JS篇」》
《如何让用户选择是否离开当前页面?「JS篇」》
《JavaScript开发人员更喜欢Deno的五大原因》
《仅用18行JavaScript实现一个倒数计时器》
《图文细说JavaScript 的运行机制》
《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》
《推荐Web程序员常用的15个源代码编辑器》
《10个实用的JS技巧「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》
《深入JavaScript教你内存泄漏如何防范》
《手把手教你7个有趣的JavaScript 项目-上「附源码」》
《手把手教你7个有趣的JavaScript 项目-下「附源码」》
《JavaScript 使用 mediaDevices API 访问摄像头自拍》
《手把手教你前端代码如何做错误上报「JS篇」》
《一文让你彻底搞懂移动前端和Web 前端区别在哪里》
《提高你的 JavaScript 技能10 个问答题》
《JavaScript图表库的5个首选》
《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》
《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》
《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》
《可视化的 js:动态图演示 Promises Async/Await 的过程》
《原生JS封装拖动验证滑块你会吗?「实践」》
《如何实现高性能的在线 PDF 预览》
《细说使用字体库加密数据-仿58同城》
《Node.js要完了吗?》
《Pug 3.0.0正式发布,不再支持 Node.js 6/8》
《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》
《JavaScript 20 年 中文版之创立标准》
《值得收藏的前端常用60余种工具方法「JS篇」》
《箭头函数和常规函数之间的 5 个区别》
《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》
《「前端篇」不再为正则烦恼》
《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》
《深入细品浏览器原理「流程图」》
《JavaScript 已进入第三个时代,未来将何去何从?》
《前端上传前预览文件 image、text、json、video、audio「实践」》
《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》
《推荐13个有用的JavaScript数组技巧「值得收藏」》
《前端必备基础知识:window.location 详解》
《不要再依赖CommonJS了》
《犀牛书作者:最该忘记的JavaScript特性》
《36个工作中常用的JavaScript函数片段「值得收藏」》
《Node + H5 实现大文件分片上传、断点续传》
《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》
《【实践总结】关于小程序挣脱枷锁实现批量上传》
《手把手教你前端的各种文件上传攻略和大文件断点续传》
《字节跳动面试官:请你实现一个大文件上传和断点续传》
《谈谈前端关于文件上传下载那些事【实践】》
《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》
《最全的 JavaScript 模块化方案和工具》
《「前端进阶」JS中的内存管理》
《JavaScript正则深入以及10个非常有意思的正则实战》
《前端面试者经常忽视的一道JavaScript 面试题》
《一行JS代码实现一个简单的模板字符串替换「实践」》
《JS代码是如何被压缩的「前端高级进阶」》
《前端开发规范:命名规范、html规范、css规范、js规范》
《【规范篇】前端团队代码规范最佳实践》
《100个原生JavaScript代码片段知识点详细汇总【实践】》
《关于前端174道 JavaScript知识点汇总(一)》
《关于前端174道 JavaScript知识点汇总(二)》
》前端174个JavaScript知识点总结(三)
》 《几个非常有意思的javascript知识点总结【实践】》 《都2020年了,你还不会JavaScript 装饰器?》 《JavaScript实现图片合成下载》 《70个JavaScript知识点详细总结(上)【实践】》 《70个JavaScript知识点详细总结(下)【实践】》 《开源了一个 JavaScript 版敏感词过滤库》 《送你 43 道 JavaScript 面试题》 《3个很棒的小众JavaScript库,你值得拥有》 《手把手教你深入巩固JavaScript知识体系【思维导图】》 《推荐7个很棒的JavaScript产品步骤引导库》 《Echa哥教你彻底弄懂 JavaScript 执行机制》 《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》 《深入解析高频项目中运用到的知识点汇总【JS篇】》 《JavaScript 工具函数大全【新】》 《从JavaScript中看设计模式(总结)》 《身份证号码的正则表达式及验证详解(JavaScript,Regex)》 《浏览器中实现JavaScript计时器的4种创新方式》 《Three.js 动效方案》 《手把手教你常用的59个JS类方法》 《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》 《深入浅出讲解 js 深拷贝 vs 浅拷贝》 《手把手教你JS开发H5游戏【消灭星星】》 《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》 《手把手教你全方位解读JS中this真正含义【实践】》 《书到用时方恨少,一大波JS开发工具函数来了》 《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》 《手把手教你JS 异步编程六种方案【实践】》 《让你减少加班的15条高效JS技巧知识点汇总【实践】》 《手把手教你JS开发H5游戏【黄金矿工】》 《手把手教你JS实现监控浏览器上下左右滚动》 《JS 经典实例知识点整理汇总【实践】》 《简单几步让你的 JS 写得更漂亮》 《恭喜你获得治疗JS this的详细药方》 《谈谈前端关于文件上传下载那些事【实践】》 《面试中教你绕过关于 JavaScript 作用域的 5 个坑》 《Jquery插件(常用的插件库)》 《【JS】如何防止重复发送ajax请求》 《JavaScript+Canvas实现自定义画板》
用户评论
我就知道JavaScript总有些小坑!这四个毫秒对我来说简直就是噩梦,特别是在需要高精度的实时应用中更是致命!
有6位网友表示赞同!
说的没错,setTimeout 的最小延迟确实存在!主要是浏览器为了保证其他任务的执行效率,会先处理一些优先级高的操作,导致 setTimeout 执行的时机稍有延后。
有11位网友表示赞同!
这和咱们平时体验到的鼠标延迟还是不一样哈,感觉更像是JavaScript自己的内部“机制”。
有16位网友表示赞同!
看来是浏览器渲染机制的问题啊!之前听说过这个说法,没想到实际是这样的解释呢,对 JavaScript 的底层实现了解了更深一层的体会。
有10位网友表示赞同!
这四大毫秒简直太关键了!我在写一些动画效果的代码的时候遇到过这个问题,延迟就会让整个动画看起来很卡顿。
有19位网友表示赞同!
学习到新东西了,原来 setTimeout 会有最小时延,难怪每次计时都不完全精确,感谢分享
有13位网友表示赞同!
其实这种情况在很多高性能应用中都会出现,我们需要考虑一些更高效的解决方案来避免这个延迟带来的影响。
有12位网友表示赞同!
这个问题困扰我很久了!我一直不明白为什么定时器执行不是立即生效的。现在明白啦,感谢博主解释!
有8位网友表示赞同!
如果时间精度特别重要,需要考虑其他的工具或技术,比如 Web Workers 或者 setInterval 的使用方法,来弥补 setTimeout 的延迟影响。
有16位网友表示赞同!
四个毫秒听起来不算多,但是对于一些对实时性要求很高的情况来说却可能会造成很大的困扰
有14位网友表示赞同!
这篇文章真的太厉害了!解释得太清楚了。我以前从来没有想过 setTimeout 会有这种限制,真是开阔我的眼界!
有6位网友表示赞同!
感觉这个延时不怎么影响一般网页的使用体验,对编程新手来说更重要的是学习JavaScript的基本语法和操作技巧吧。
有5位网友表示赞同!
其实,对于一些简单应用,这个 4ms 的延迟并不明显。主要要注意的是在那些对时间精度要求非常高的场景下才能引起重视。
有16位网友表示赞同!
我一直在用setTimeout做一些动画效果,现在明白了为什么有些动画看起来会卡顿一点,原来是这个原因!谢谢博主分享!
有7位网友表示赞同!
有时候会遇到因为 setTimeout 的延迟而导致程序出现奇怪的问题,所以这个机制我们需要理解清楚!学习了!
有19位网友表示赞同!
四个毫秒的延迟对于一些高性能场景确实比较致命! 不过,也有一些技巧可以用来缓解这个问题,比如将操作分散到不同的事件循环中执行
有8位网友表示赞同!
我感觉这篇文章写的很好的,特别是对setTimeout最小延时的解释非常清楚!这对学习JavaScript编程帮助很大!
有13位网友表示赞同!
了解了setTimeout的延迟机制,有助于更有效地使用它! 谢谢博主的详细讲解!
有10位网友表示赞同!