setTimeout、setInterval、requestAnimationFrame,定时器不准时

目录

定时器(func[, delay, arg1, arg2, ...])

区别

setTimeout固定时长后执行

setInterval间隔固定时间重复执行

相同

最短时长设定为4ms(不超过4次调用,时长可以小于4ms)

相同的对象id池共享

定时器不准的原因

执行超时

前一个任务

自身任务

节流机制:浏览器强制执行最小超时

HTML 标准:定时嵌套调用被安排了 5 次及以上(定时器最短时长设定为4ms)

非活动标签(除了后台播放):优化加载损耗,降低耗电

Firefox 桌面版和 Chrome :1 秒的最小超时值

Firefox 安卓版:15 分钟的超时,甚至卸载它们

标签中包含 AudioContext,Firefox 不会节流

追踪型脚本:跟踪用户行为

Firefox 在前台运行最小延迟仍然是 4ms。

Firefox 在后台标签最小延迟是 10 秒(在文档首次加载后 30 秒开始生效)

动画卡顿:刷新频率≠时间间隔

解决:requestAnimationFrame刷新频率一致

requestAnimationFrame:浏览器专门为动画提供的API

与定时器区别

1)引擎层面

setTimeout: JS引擎 ,存在事件轮询

requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的: GUI引擎在渲染时会阻塞JS引擎的计算

2)性能层面

当页面被隐藏或最小化时:定时器 setTimeout仍会在后台执行动画任务

当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停:requestAnimationFrame也会停止

delay=0/不设置delay默认为0:下一个事件循环执行

nextTick(Vue):DOM更新后执行回调函数

实现nectTick

promise>MutationObserver>setImmediate>setTimeout


setTimeout() 全局函数 - Web API 接口参考 | MDN

定时器(func[, delay, arg1, arg2, ...])

delay=Number(delay)

arg附加参数,一旦定时器到期,它们会作为参数传递给 functionRef 指定的函数

区别

setTimeout固定时长后执行

setInterval间隔固定时间重复执行

相同

最短时长设定为4ms(不超过4次调用,时长可以小于4ms)

相同的对象id池共享

timeoutID 值(正整数)不会被同一对象(window 或 worker)的后续调用 setTimeout() 或 setInterval() 重复使用。然而,不同的对象使用不同的 ID 池

setTimeout() 和 setInterval() 使用共享的 ID 池,意味着在技术上可以混用 clearTimeout() 和 clearInterval()。但是,为了清楚起见,应该避免这样做

定时器不准的原因

执行超时

前一个任务

setTimeout/setInterval是宏任务,根据事件轮询机制优先级,其他任务会阻塞或延迟js任务的执行

function foo() {
  console.log("foo 被调用");
}
setTimeout(foo, 0);
//很费时间的js操作
console.log("setTimeout 之后");
//输出
...超时...
setTimeout 之后
foo 被调用

自身任务

考虑极端情况,假如定时器里面的代码需要进行大量的计算,或者是DOM操作,代码执行时间超过定时器的时间,会出现定时器不准的情况

节流机制:浏览器强制执行最小超时

HTML 标准:定时嵌套调用被安排了 5 次及以上(定时器最短时长设定为4ms)

let nestedCount = 0;

function nestedTimeout() {
    nestedCount++;
    console.log(`Nested call ${nestedCount}`);

    if (nestedCount < 5) {
        // 继续嵌套调用 setTimeout
        setTimeout(nestedTimeout, 0);
    }
}

// 初始调用
nestedTimeout();

非活动标签(除了后台播放):优化加载损耗,降低耗电

活动标签:正在浏览的标签页。

非活动标签:在后台运行的标签页。

Firefox 桌面版和 Chrome :1 秒的最小超时值
Firefox 安卓版:15 分钟的超时,甚至卸载它们
标签中包含 AudioContext,Firefox 不会节流

追踪型脚本:跟踪用户行为

追踪型脚本通常是用于跟踪用户行为、分析网站访问情况等目的的脚本。

Firefox 在前台运行最小延迟仍然是 4ms。
Firefox 在后台标签最小延迟是 10 秒(在文档首次加载后 30 秒开始生效)

动画卡顿:刷新频率≠时间间隔

不同设备的屏幕刷新频率可能不同, setTimeout/setInterval只能设置固定的时间间隔,这个时间和屏幕刷新间隔可能不同

setTimeout/setInterval通过设置一个间隔时间,来不断改变图像实现动画效果,在不同设备上可能会出现卡顿、抖动等现象

解决:requestAnimationFrame刷新频率一致

requestAnimationFrame:浏览器专门为动画提供的API

requestAnimationFrame刷新频率与显示器的刷新频率保持一致,使用该api可以避免使用setTimeout/setInterval造成动画卡顿的情况

requestAnimationFrame:告诉浏览器在下次重绘之前执行传入的回调函数(通常是操纵dom,更新动画的函数)

与定时器区别

1)引擎层面

setTimeout: JS引擎 ,存在事件轮询

requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的: GUI引擎在渲染时会阻塞JS引擎的计算

这样设计的原因,如果在GUI渲染的时候,JS同时又改变了dom,那么就会造成页面渲染不同步

2)性能层面

当页面被隐藏或最小化时:定时器 setTimeout仍会在后台执行动画任务
当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停:requestAnimationFrame也会停止

delay=0/不设置delay默认为0:下一个事件循环执行

nextTick(Vue):DOM更新后执行回调函数

实现nectTick

promise>MutationObserver>setImmediate>setTimeout
  1. Promise:如果浏览器支持PromisenextTick会优先使用Promise.then来创建微任务,以确保回调函数在下一个微任务队列中执行。

  2. MutationObserver:如果浏览器不支持PromisenextTick会检查是否支持MutationObserverMutationObserver变动观察器)是一种Web API,它允许开发者监视DOM树的变化并在这些变化发生时执行回调函数允许监视DOM树的变化,因此它也可以用于异步任务的调度。nextTick会尝试使用MutationObserver来创建微任务。

  3. setImmediate:如果浏览器既不支持Promise也不支持MutationObservernextTick会检查是否支持setImmediatesetImmediate是一种宏任务,通常比setTimeout执行得更早,因此它用于创建宏任务级别的异步任务。

  4. setTimeout:如果以上方法都不可用,nextTick会回退到使用setTimeout来创建异步任务。setTimeout是一种宏任务,但是优先级较低,可能在其他异步任务之后执行。

// 定义nextTick的回调队列
let callbacks = [];

// 批量执行nextTick的回调队列
function flushCallbacks() {
  callbacks.forEach((cb) => cb());
  callbacks = [];
  pending = false;
}

//定义异步方法,优先使用微任务实现
let timerFunc;

// 优先使用promise 微任务
if (Promise) {
  timerFunc = function () {
    return Promise.resolve().then(flushCallbacks);
  };
  // 如不支持promise,再使用MutationObserver 微任务
} else if (MutationObserver) {
  timerFunc = function () {
    const textNode = document.createTextNode('1');
    const observer = new MutationObserver(() => {
      flushCallbacks();
      observer.disconnect();
    });
    const observe = observer.observe(textNode, { characterData: true });
    textNode.textContent = '2';
  };
  // 微任务不支持,再使用宏任务实现
} else if (setImmediate) {
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else {
  timerFunc = function () {
    setTimeout(flushCallbacks);
  };
}

// 定义nextTick方法
export function nextTick(cb) {
  callbacks.push(cb);
  if (!pending) {
    pending = true;
    timerFunc();
  }
}

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值