前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >setTimeout实现原理和使用注意

setTimeout实现原理和使用注意

作者头像
winty
发布于 2019-12-20 09:00:05
发布于 2019-12-20 09:00:05
1.8K00
代码可运行
举报
文章被收录于专栏:前端Q前端Q
运行总次数:0
代码可运行

setTimeout,它就是一个定时器,用来指定某个函数在多少毫秒之后执行。

setTimeout用法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID = setTimeout(function[, delay]);
var timeoutID = setTimeout(code[, delay]);
  • 第一个参数为函数或可执行的字符串(比如alert('test'),此法不建议使用)
  • 第二个参数为延迟毫秒数,可选的,默认值为0.
  • 第三个及后面的参数为函数的入参。
  • setTimeout 的返回值是一个数字,这个值为timeoutID,可以用于取消该定时器。

setTimeout在浏览器中的实现

浏览器渲染进程中所有运行在主线程上的任务都需要先添加到消息队列,然后事件循环系统再按照顺序执行消息队列中的任务。

在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列(我们可以称为延迟队列),这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务。所以当通过 JavaScript 创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。

比如这样的一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function foo(){
  console.log("test")
}
var timeoutID = setTimeout(foo,100);

当通过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会创建一个回调任务,包含了回调函数foo、当前发起时间、延迟执行时间等,其模拟代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct DelayTask{
  int64 id;
  CallBackFunction cbf;
  int start_time;
  int delay_time;
};
DelayTask timerTask;
timerTask.cbf = foo;
timerTask.start_time = getCurrentTime(); //获取当前时间
timerTask.delay_time = 100;//设置延迟执行时间

创建好回调任务之后,就会将该任务添加到延迟执行队列中。那这个回调任务,什么时候会被执行呢? 浏览器中有个函数是专门用来处理延迟执行任务的,暂且称为ProcessDelayTask,它的主要逻辑如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void ProcessTimerTask(){
  //从delayed_incoming_queue中取出已经到期的定时器任务
  //依次执行这些任务
}

TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
  for(;;){
    //执行消息队列中的任务
    Task task = task_queue.takeTask();
    ProcessTask(task);

    //执行延迟队列中的任务
    ProcessDelayTask()

    if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
        break; 
  }
}

其实就是,当浏览器处理完消息队列中的一个任务之后,就会开始执行 ProcessDelayTask 函数。ProcessDelayTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。这样定时器就实现了,从这个过程也可以明显看出,定时器并不一定是准时延后执行的

注意事项

  1. 如果当前任务执行时间过久,会延迟到期定时器任务的执行

在使用 setTimeout 的时候,有很多因素会导致回调函数执行比设定的预期值要久,其中一个就是上文说到的,如果处理的当前任务耗时过长,定时器设置的任务就会被延后执行。 比如在浏览器中执行这样一段代码,并打印执行时间:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function bar() {
    console.log('bar')
    const endTime = Date.now()
    console.log('cost time',endTime - startTime)
}
function foo() {
    setTimeout(bar, 0);
    for (let i = 0; i < 5000; i++) {
        let i = 5+8+8+8
        console.log(i)
    }
}
foo()

执行结果如图:

从结果可以看到,执行 foo 函数所消耗的时长是 365 毫秒,这也就意味着通过 setTimeout 设置的任务被推迟了 365 毫秒才执行,而设置 setTimeout 的回调延迟时间是 0。

  1. 使用 setTimeout 设置的回调函数中的 this 环境不是指向回调函数 比如这段代码:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var name= 1;
var MyObj = {
  name: 2,
  test:1,
  showName: function(){
    console.log(this.name,this.test);
  }
}
setTimeout(MyObj.showName,1000)
MyObj.showName()
//先输出 2 1
// 1s后输出 1 undefined 

这里其实认真分析一下,也很好理解这个 this 的指向。按照 this 的规定,如果是对象调用(obj.fn()),那么this指向该对象,因此MyObj.showName()输出的是 MyObj 里面的值。在 setTimeout 中,入参是MyObj.showName,这里是把这个值传了进去,可以理解为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fn = MyObj.showName
setTimeout(fn,1000)

这样看,在setTimeout里面,当执行到的时候,实际上就是在window下执行fn,此时的this,就指向了window,而不是原来的函数。

  1. setTimeout 存在嵌套调用问题

如果 setTimeout 存在嵌套调用,调用超过5次后,系统会设置最短执行时间间隔为 4 毫秒。 我们可以在浏览器粗略测试一下,有如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let startTime = Date.now()
function cb() { 
  const endTime = Date.now()
  console.log('cost time',endTime - startTime)
  startTime = startTime
  setTimeout(cb, 0); 
}
setTimeout(cb, 0);

执行结果:

从结果可以看出,前面五次调用的时间间隔比较小,嵌套调用超过五次以上,后面每次的调用最小时间间隔是 4 毫秒(我运行的结果,间隔基本是 5ms,考虑有代码执行的计算误差)。

之所以出现这样的情况,是因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。可以看下源码(https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/frame/dom_timer.cc)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static const int kMaxTimerNestingLevel = 5;

// Chromium uses a minimum timer interval of 4ms. We'd like to go
// lower; however, there are poorly coded websites out there which do
// create CPU-spinning loops.  Using 4ms prevents the CPU from
// spinning too busily and provides a balance between CPU spinning and
// the smallest possible interval timer.
static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);

所以,一些实时性较高的需求就不太适合使用 setTimeout 了,比如你用 setTimeout 来实现 JavaScript 动画就不一定是一个很好的主意。

  1. 未激活的页面,setTimeout 执行最小间隔是 1000 毫秒

如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量。这一点你在使用定时器的时候要注意。

  1. 延时执行时间有最大值

Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,这导致定时器会被立即执行。如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let startTime = Date.now()
function foo(){
  const endTime = Date.now()
  console.log('cost time',endTime - startTime)
  console.log("test")
}
var timerID = setTimeout(foo,2147483648);//会被立即调用执行

执行结果:

运行后可以看到,这段代码是立即被执行的。但如果将延时值修改为小于 2147483647 毫秒的某个值,那么执行时就没有问题了。

参考资料

  • 极客时间《浏览器工作原理与实践》
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端Q 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
浏览器原理学习笔记04—浏览器中的页面事件循环系统
每个渲染进程都有一个非常繁忙的主线程,需要一个系统来统筹调度任务(具体任务后面详解)
CS逍遥剑仙
2020/05/02
1.6K0
浏览器工作原理 - 页面循环系统
每个渲染进程都有一个主线程,并且主线程很忙,既要处理 DOM,又要计算样式,还要处理布局,同时还要处理 JavaScript 任务及各种输入事件。要让这么多不同类型的任务在主线程中顺利执行,需要一个系统来统筹调度这些任务 —— 消息队列和事件循环系统。
Cellinlab
2023/05/17
6980
浏览器工作原理 - 页面循环系统
JavaScript 事件循环竟还能这样玩!
JavaScript 是一种单线程的编程语言,这意味着它一次只能执行一个任务。为了能够处理异步操作,JavaScript 使用了一种称为事件循环(Event Loop)的机制。
沉浸式趣谈
2024/06/03
1070
JavaScript 事件循环竟还能这样玩!
为什么要用 setTimeout 模拟 setInterval ?
来源:九旬 https://segmentfault.com/a/1190000038829248
@超人
2021/02/26
1.2K0
为什么要用 setTimeout 模拟 setInterval ?
为什么要用 setTimeout 模拟 setInterval ?
在[JS 事件循环之宏任务和微任务](../Performance/JS事件循环之宏任务和微任务.html)中讲到过,setInterval 是一个宏任务。
九旬
2021/01/06
1.2K0
JavaScript设置定时器、取消定时器及执行机制解析
今天整理了一下 JavaScript 定时器,顺便了解了一下 JavaScript 的运行机制,现在记录一下。
德顺
2019/11/12
5K0
如何实现比 setTimeout 快 80 倍的定时器?
很多人都知道,setTimeout 是有最小延迟时间的,根据 MDN 文档 setTimeout:实际延时比设定值更久的原因:最小延迟时间 中所说:
ssh_晨曦时梦见兮
2023/10/14
2130
如何实现比 setTimeout 快 80 倍的定时器?
如何实现比 setTimeout 快 80 倍的定时器?
很多人都知道,setTimeout 是有最小延迟时间的,根据 MDN 文档 setTimeout:实际延时比设定值更久的原因:最小延迟时间[1] 中所说:
winty
2021/05/18
1.2K0
如何实现比 setTimeout 快 80 倍的定时器?
你不知道的setTimeout
setTimout 在日常开发中或多或少都会用到,以前可能仅限于使用,但是对其原理了解的比较浅,因此此文会更加深入的去了解其作用和原理。
前端知知
2022/09/29
2910
从setTimeout分析浏览器线程
[toc]   今天接到阿里的面试电话,面试官很和善,聊聊天的形式不知不觉就是一个小时。本人接触前端不深,面试的时候问的几个问题也让我发现自身学习过程中思考太少,其中一个就是问到了setTimeout的工作机理,当时简单讲了讲我自己的想法,面试官也指出了其中的问题,现查阅资料重新整理记录。
csxiaoyao
2019/02/15
1.2K0
从setTimeout分析浏览器线程
setTimeout和requestAnimationFrame
JavaScript语言的一大特点就是单线程,也就是说,同一时间只能做一件事,前面的任务没做完,后面的任务只能等着。
木子星兮
2020/07/16
1.8K0
TypeScript延迟执行工具类
在前端开发中,我们经常需要处理一些延迟执行、防抖和节流的场景。今天介绍一个实用的Delay工具类,它提供了这些常用的延迟执行功能。
訾博ZiBo
2025/01/06
1460
你所不知道的setTimeout
JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。它们向任务队列添加定时任务。初始接触它的人都觉得好简单,实时上真的如此么?这里记载下,一路对其使用姿势变迁的历程。 1, setTimeout()基础 setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。 var timerId = setTimeout(func|code, de
晚晴幽草轩轩主
2018/03/27
1.9K0
【THE LAST TIME】彻底吃透 JavaScript 执行机制
首先我们需要声明下,JavaScript 的执行和运行是两个不同概念的,执行,一般依赖于环境,比如 node、浏览器、Ringo 等, JavaScript 在不同环境下的执行机制可能并不相同。而今天我们要讨论的 Event Loop 就是 JavaScript 的一种执行方式。所以下文我们还会梳理 node 的执行方式。而运行呢,是指JavaScript 的解析引擎。这是统一的。
Nealyang
2019/09/29
4710
【THE LAST TIME】彻底吃透 JavaScript 执行机制
JavaScript基础-定时器:setTimeout, setInterval
在JavaScript的世界里,定时器是实现异步编程不可或缺的工具,它允许我们按计划执行某些代码片段。setTimeout和setInterval作为两大核心定时器函数,广泛应用于页面动画、定时更新、延时操作等多种场景。本文将深入浅出地介绍这两个函数的基本用法、常见问题、易错点及避免策略,并通过代码示例加以说明。
Jimaks
2024/06/14
5380
JavaScript基础-定时器:setTimeout, setInterval
详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
当时的我年轻气盛,在简历上放了自己的博客地址,而面试官应该是翻了我的博客,好几道面试题都是围绕着我的博文来提问
用户6256742
2024/06/22
3630
setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop
笔者以前面试的时候经常遇到写一堆setTimeout,setImmediate来问哪个先执行。本文主要就是来讲这个问题的,但是不是简单的讲讲哪个先,哪个后。笼统的知道setImmediate比setTimeout(fn, 0)先执行是不够的,因为有些情况下setTimeout(fn, 0)是会比setImmediate先执行的。要彻底搞明白这个问题,我们需要系统的学习JS的异步机制和底层原理。本文就会从异步基本概念出发,一直讲到Event Loop的底层原理,让你彻底搞懂setTimeout,setImmediate,Promise, process.nextTick谁先谁后这一类问题。
蒋鹏飞
2020/10/15
1.1K0
setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop
从一个超时程序的设计聊聊定时器的方方面面
企业项目开发中经常有这样一个逻辑场景:在界面上显示倒计时,时间到了给出提示,禁止用户操作。
LIYI
2022/03/08
1.5K0
从一个超时程序的设计聊聊定时器的方方面面
js异步编程面试题你能答上来几道
在上一节中我们了解了常见的es6语法的一些知识点。这一章节我们将会学习异步编程这一块内容,鉴于异步编程是js中至关重要的内容,所以我们将会用三个章节来学习异步编程涉及到的重点和难点,同时这一块内容也是面试常考范围。
loveX001
2022/10/10
4990
字节面试:如何实现准时的setTimeout
最近有同学在面试的时候被问到了这个问题。所以我们利用这篇文章对这个问题进行下解答。
winty
2023/11/30
6350
字节面试:如何实现准时的setTimeout
推荐阅读
相关推荐
浏览器原理学习笔记04—浏览器中的页面事件循环系统
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档