「前端进阶」你不知道的 setTimeout、setInterval、requestAnimationFrame 您所在的位置:网站首页 setinterval会立即执行吗 「前端进阶」你不知道的 setTimeout、setInterval、requestAnimationFrame

「前端进阶」你不知道的 setTimeout、setInterval、requestAnimationFrame

2024-01-17 03:30| 来源: 网络整理| 查看: 265

引言

在日常开发中,我们常常会用到js有关定时器的API(setTimeout setInterval),我们更多的只会停留在使用层面,很少去仔细思考两者之间的区别、优点、缺点。

还有,不知道你有没有思考过这个问题:明明可以用setTimeout setInterval Css3完成的动画效果,为啥HTML5又提供了requestAnimationFrame🤔?

今天本文就带着大家一起深入了解setTimeout、setInterval、requestAnimationFrame。

动画前置知识

在此简单介绍一下形成动画的原因和基本概念,方便后面阅读理解。

1. 计算机屏幕刷新率与浏览器重绘次数 屏幕刷新率指1s内屏幕刷新的次数。 一般的电脑的屏幕刷新率为1s 60次(1000ms / 60 ≈ 16.7ms | 60FPS),也就是每16.7ms会刷新一下屏幕。当然此数值受到分辨率、显卡、屏幕尺寸等其他因素的影响。 由于一般的电脑的刷新频率是60FPS,所以大多数浏览器会限制其重绘次数,一般不会超过计算机的重绘次数,因为即使超过了其频率,用户的体验也不会得到提升。 2. 动画是如何形成的?

动画是由于肉眼导致的视觉残留,通过连续播放的静态图像形成的动态幻觉。当1s中连续播放24张图片时(24FPS),即可形成流畅的动画,通常来说计算机的刷新频率是60FPS。

3.web实现动画的方式 css:animation、transition js: setTimeout、setInteval html: canvas、svg requestAnimationFrame等...

本文将重点介绍setTimeout、setInterval、requestAnimationFrame这三种API。

正文 setInterval

setInterval方法按照指定的周期(毫秒)来调用函数或执行一段代码段(eval)。。

敲重点:定时器指定的时间间隔,表示的是何时将回调函数添加到消息队列,而不是何时执行回调函数。 真正何时执行函数的时间是不能确定的,取决于该回调函数何时被主线程的事件循环取到,并执行。

参数 function/code 【必需】要重复调用的函数/字符串。当为字符串时,会被编译为js代码执行。 delay 【必需】周期性调用function/code的时间间隔,以毫秒计数。 注:HTML5规定,执行时间间隔最小为10ms当小于10ms时,默认为10ms。 args1... argsN 【非必需】传递给执行函数的参数 // 每1000ms,控制台打印1 setInterval(function(){ console.log(1); }, 1000); setInterval缺点 1.存在无用调用,浪费性能

会一直不停的执行函数,即使将浏览器最小化,或者切换到另一个tab,定时器还依旧会继续在后台执行。除非关闭网页才会停止调用。

2.忽略错误代码,即使出错还会调用

setInterval中执行的代码如果出错,不会停止运行,而是继续调用。

3.无法保证调用的时间间隔相同;某次回调可能会被跳过

敲重点:setInterval每次将回调函数推入异步队列前,会检查异步队列中是否有该定时器的代码实例,如果存在,则不会添加本次回调函数。

如果回调函数的执行需要花费很大时间执行,某些处于中间的调用会被忽略。

例:分析如下代码执行步骤

...some event... setInterval(T, 100); // 代表每100ms将T函数推入异步队列中

img.png

代码开始执行,先执行some event同步代码,100ms后将T1添加到异步队列的尾部; 此时主线程依旧有event任务在执行,所以无法立即执行T1。只能待主线程任务结束后,执行T1; 又过了100ms,T1在主线程执行,此时将T2添加到异步队列尾部,由于T1还在执行,所以T2只能等待执行; 又过了100ms,此时本应该将T3添加到异步队列尾部,但是由于异步队列中存在T2,所以T3不会被添加到队列中(被跳过)。 T1执行完毕后,立即从异步队列中取出T2执行(T1连着T2执行,并没有达到定时器的效果)

由以上的例子我们可以看出setInterval的两个缺点:

某些极端情况下,无法保证按照时间间隔运行回调函数; 当回调函数执行时间过长时,某次的回调可能被直接忽略。 使用setTimeout替代setInterval /** * 使用setTimeout模拟setInterval计时器 * @param fn * @param delay * @param args * @returns {{clear: (function(): void)}} * @private */ function _interval(fn, delay, ...args){ let timerId; function callback(){ fn(...args); timerId = setTimeout(callback, delay) } timerId = setTimeout(callback, delay); // 清除计时器方法 return { clear:() => clearTimeout(timerId) }; } // 开始计时器 const timer = _interval(function(){ console.log(1); }, 1000); // 清除该计时器 setTimeout(timer.clear, 5 * 1000); setTimeout

setTimeout设置一个定时器,该定时器在指定时间到期后执行一个函数或者一段代码。

敲重点:定时器指在延迟时间后会将回调函数添加到异步队列中,真正的执行时机需要等到主线程为空后取出再执行。 所以 真正的执行的时间 >= 延迟时间

参数 function/code 【必需】要重复调用的函数/字符串。当为字符串时,会被编译为js代码执行。 delay 【必需】周期性调用function/code的时间间隔,以毫秒计数。 注:HTML5规定,执行时间间隔最小为4ms当小于4ms时,默认为4ms。 args1... argsN 【非必需】传递给执行函数的参数 // 1000ms后将函数添加到异步队列中,打印1 setTimeout(function(){ console.log(1); }, 1000); setTimeout缺点 1.执行时间不能确定 2.动画在某些机型上可能存在卡顿、丢帧、抖动的现象。

如开篇所述,动画的本质是:肉眼导致的视觉残留,通过连续播放的静态图像形成的动态幻觉。我们有时会感到卡顿是因为帧率不够。

虽然可以通过设定固定间隔setTimeout(fn, 16.7)的方式,设置时间间隔与大部分计算机刷新频率同步。但是依旧会受到以下因素的影响:

由于JS引擎线程是异步的,setTimeout本身是异步任务,需要等待主线程的任务执行完毕后才可以执行。所以其回调真实的开始执行时间 >= 16.7ms 不同的机器,其刷新频率是不同的,setTimeout只能写死一个时间,不够准确

以上的情况都会使setTimeout执行的时间间隔和浏览器刷新频率不同步,导致动画卡顿、丢帧、抖动的现象。

那么有没有动画的终极神器呢?下面来介绍requestAnimationFrameAPI。

requestAnimationFrame(rFA)

虽然我们可以使用CSS3的animation、transition属性来实现动画,但是如果遇到"请将滚动条匀速的返回到顶部"的需求,CSS3就鞭长莫及了。因为CSS3无法控制scrollTop属性。这时就要用到requestAnimationFrame这个神器了。

window.requestAnimationFrame() 告诉浏览器:你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

参数 callback 【必需】下一次重绘之前要执行的函数,该函数默认被传入一个performance参数,用来测试网页性能。 使用示例

如下代码只会再浏览器第一次刷新时调用,只会执行一次:

window.requestAnimationFrame(function () { console.log(this); // 只执行一次 打印window })

如果想形成连续的动画,需要在rAF中回调函数中,再次调用自己

如果想与浏览器刷新频率同步调用,需这样写:

// 不断打印出 window function animateFn (){ console.log(this); window.requestAnimationFrame(animateFn); } window.requestAnimationFrame(animateFn); 优点 1.执行时机由浏览器决定,与浏览器刷新频率保持同步,不会有丢帧、卡顿的情况

与setTImeout、setInterval不同,requestAnimationFrame回调函数的调用时机不是由开发者定义,是由浏览器决定的。

如果该机器的刷新的频率为60FPS,requestAnimationFrame的回调函数就会1 / 60 ≈ 16.7ms左右执行一次; 如果机器的刷新频率为80FPS,requestAnimationFrame的回调函数就会1 / 80 ≈ 12.5ms左右执行一次;

这样的机制,可以与浏览器刷新频率同步,不会导致丢帧、卡顿的情况。

2.节省CPU资源

与setTimeout、setInterval不同,当网页被最小化,或是当前tab处于"未激活"的状态时,该页面的刷新任务会被系统暂停,requestAnimationFrame也会停止渲染,节省CPU资源。 当tab重新被"激活"后,requestAnimationFrame会继续渲染。

3.高频函数节流

对于resize、scroll高频触发事件来说,使用 requestAnimationFrame可以保证在每个绘制区间内,函数只被执行一次,节省函数执行的开销。 如果使用setTimeout、setInterval可能会在浏览器刷新间隔中有无用的回调函数调用,浪费资源。

cancelAnimation(id) 取消rFA

与setTimeout、setInterval相同,requestAnimationFrame执行完后会返回一个代表此次执行的唯一id,可以用此id取消rFA。

const id = requestAnimationFrame(function(){}); cancelAnimationFrame(id); 写个🌰

使用rFA,实现点击div开始向右运动,点击停止,再次点击继续运动:

Document .box{ width: 100px; height: 100px; background: paleturquoise; position: absolute; left: 0; } (function (window, document) { const oBox = document.getElementsByClassName('box')[0]; let animationId; oBox.addEventListener('click', function () { animationId ? cancelAnimation() : startAnimation(oBox); }); /** * 开始运动 * @param element dom元素 */ function startAnimation(element) { element.style.left = parseInt(window.getComputedStyle(element).getPropertyValue("left")) + 1 + 'px'; animationId = requestAnimationFrame(() => startAnimation(element)) } /** * 取消运动 */ function cancelAnimation() { cancelAnimationFrame(animationId); animationId = null; } })(window, document);

效果: Dec-22-2021 15-33-37.gif

写在最后

看到这里,希望本文对你有一些帮助😁。如果文章中有错误,麻烦评论指出,一起进步~~~~。

我是抹茶,不断学习的一名coder✌🏻。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有