0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

js基础之setTimeout与setInterval原理分析

京东云 来源:jf_75140285 作者:jf_75140285 2024-09-19 15:10 次阅读

setTimeout与setInterval概述

setTimeout与setInterval是JavaScript引擎提供的两个定时器方法,分别用于函数的延时执行和循环调用。前者的主要思想是通过一个定时器,让函数在计时结束后再执行;后者则是每隔一定的时间,就启动一次函数的执行。

从原理来看,两者似乎并不复杂。但由于JavaScript引擎是单线程的,这就让上述两个定时器的实际执行变得稍微复杂了一些。下面我们来看一下两者的运行机制与需要注意的问题。

基本原理

知识铺垫

单线程模型:由于JavaScript被设计为用在浏览器环境,而该环境下存在大量可能发生冲突的DOM操作,为了避免进行复杂的冲突处理(可能存在的冲突数量几乎不可预测),JavaScript的设计者舍弃了java的多线程模型(该模型下,执行引擎同时可以做几件事,但要进行线程同步),将其设计成了一门单线程语言(执行引擎在同一时间只做一件事)。

注意:这里的单线程是指JavaScript的主线程只有一个。除了这个主线程,JavaScript还有一个I/O线程,通过事件循环来处理I/O问题,但两者之间相对独立,不需要进行状态同步,因此我们仍然可以把JavaScript看成一门单线程语言。

任务队列:所谓任务队列,就是用于存储等待执行的任务的队列。由于JavaScript是一门单线程语言,如果当前有一个任务需要执行,但JavaScript引擎正在执行其他任务,那么这个任务就需要放进一个队列中进行等待。等到线程空闲时,就可以从这个队列中取出最早加入的任务进行执行(类似于我们去银行排队办理业务。单线程相当于说这家银行只有一个服务窗口,一次只能为一个人服务,后面到的就需要排队,而任务队列就是排队区,先到的就优先服务)。

注意:如果当前线程空闲,并且队列为空,那每次加入队列的函数将立即执行。

setTimeout与setInterval

setTimeout(func, delay, args) :设置超时调用。如对于setTimeout(func, 100, args),js引擎会为func函数设置一个计时器,100毫秒后,将func添加到任务队列等待执行。

setInterval(func, interval, args) :设置循环调用。对于语句setInterval(func, 100, args),js引擎每隔100毫秒就会把func添加到任务队列一次。

相同点:

两者都会加入同一个队列,等待线程空闲时执行。

两者都无法保证在何时执行回调,因为无法知道线程何时空闲。

不同点

setTimeout只会将函数添加到任务队列一次,而setInterval则是循环往队列中添加函数。

setTimeout可以保证函数在指定的时间间隔内不会执行,而setInterval无法保证(有可能出现接近连续执行的情况,后面会分析原因)。

运行机制

setTimeout

setTimeout的运行机制相对简单,即在执行该语句时,设置一个定时器,定时时间置为所设置的延时,当计时结束后,将传入的函数加入任务队列,之后的执行就交给任务队列负责。

setTimeout函数本身会返回一个句柄,我们可以在函数执行前通过向clearTimeout传入该句柄取消函数的执行。示例代码如下:

function func(message){
	;
}
//设置100毫秒后执行func函数
var timer = setTimeout(func, 100, "你好");

function cancel(){
	clearTimeout(timer);   //取消超时调用
}

上述代码将在100毫秒后执行func函数,弹出一个内容为"你好"的对话框。如果在100毫秒内调用了cancel,就可以取消func函数的执行。

setInterval

setInterval本质上就是每隔一定的时间向任务队列添加回调函数。但setInterval有一个原则:在向队列中添加回调函数时,如果队列中存在之前由其添加的回调函数,就放弃本次添加(不会影响之后的计时)。另外也可以通过clearInterval方法移除定时器,使用方法同clearTimeout。

由于setInterval只负责定时向队列中添加函数,而不考虑函数的执行,那么我们考虑一下下面的情况:

假设线程执行完setInterval(func, 100, args)后处于完全空闲状态(即只要向任务队列添加函数就会立即执行)。而func是一个相对复杂的函数,执行该函数需要90毫秒。那么函数的执行过程就会变成下图所示:

chaijie_default.png

从图中可以看到,从上次函数执行完毕,到下次开始执行,之间只间隔了10毫秒,而不是我们所希望的每隔100毫秒执行一次(因为setInterval只关注任务添加,不关注任务执行)。

由于上述机制,在很多情况下,setInterval都会遇到一些性能问题。就拿上面的例子来说,我们的本意可能是每隔100毫秒执行一次函数,结果只等待了10毫秒就又执行了一次。另外,对于复杂的实际情况,setInterval经常出现两次的执行间隔相差甚远的情况,对于用户能感知到的操作,这会带来很不好的用户体验。因此在实际编码中,开发者通常会使用setTimeout来模拟实现setInterval效果(下面会有举例)。

而如果线程一开始是繁忙的,直到150毫秒处才进入空闲状态(假设func执行时长为10毫秒),那么实际的运行将变成下图所示:

chaijie_default.png

这里在100毫秒处向队列添加func时,由于线程繁忙,上次添加的func还在队列中等待,因此直接丢弃本次要添加的函数,但在200毫秒时仍然重新向队列中添加func。

应用场景

setTimeout

setTimeout主要用于需要进行延时调用的场景中。如之前一篇文章介绍的js基础之函数的节流与防抖,就是setTimeout典型的应用场景。此外,由于setInterval存在的性能问题,在实际的编码中,开发人员通常会使用setTimeout来模拟setInterval,以防止出现函数连续执行的情况。如对于下面的代码:

function func(args){
  //函数本身的逻辑
  ...
}
var timer = setInterval(func, 100, args);

我们可以通过以下代码来实现:

var timer;
function func(args){
  //函数本身的逻辑
  ...
  //函数执行完后,重置定时器
  timer = setTimeout(func, 100, args);
}
timer = setTimeout(func, 100, args);

利用setTimeout保证在指定的时间内不会执行的特点,我们可以在执行完上次的回调函数后,重置定时器,实现循环执行func的效果,并且从上次执行完毕到下次执行开始,至少会经过100毫秒。这在实际的编码中通常会带来较大的性能提升,同时函数的执行间隔也会相对稳定。

setInterval

尽管存在上述性能问题,setInterval的使用场景相对较少,但当所使用的接口来自外部(即回调函数本身无法修改)时,就必须通过setInterval来实现循环执行了。此外,对于动画效果来说,我们通常会希望动画运行的更加平滑(也就是希望函数运行得更频繁),这时使用setInterval往往更加流畅,具体请参考之前的文章使用原生js实现简单动画效果。

除了这类情况,开发者一般不会使用setInterval方法进行循环调用。

补充说明

setTimeout与setInterval的第一个参数可以是一个匿名函数,也可以是一个函数名,或者是一个字符串,如下面的写法都是合法的:

function func(msg){
  ...
}
//传入回调函数名
setTimeout(func, 100, "夕山雨");
//传入匿名函数
setTimeout(function(name){
  ...
}, 100, "夕山雨");
//传入字符串,js引擎会将其解析为函数体
setTimeout("", 100);

但是传入如下的格式就可能报错:

setTimeout(func("夕山雨"), 100);

因为这种写法实际上是先调用func函数,然后再将返回值添加到任务队列。如果func的返回值不是函数(或可执行的字符串),那么程序就会报错;如果返回值是函数,则会将返回的函数添加到任务队列。该情况可以写成下面的形式:

//将其作为字符串传入,就可以被正确解析
setTimeout("func('夕山雨')", 100);

此外,当给setTimeout传入的延迟时间为0时,并不代表回调函数会立即执行。实际上浏览器规定的有一个默认的最短计时时间,对于现代浏览器,这个时间一般为4毫秒(老版本的浏览器则会更长一些)。也就是说,即使传入的延迟时间为0,浏览器也会至少在4毫秒后才会执行。

上述补充说明同样适用于setInterval。

总结

setTimeout与setInterval都是通过一个定时器控制回调函数的执行,但由于javascript单线程的特点,两者都不能准确控制函数的执行时间点,这点还请开发者注意。如果函数只需要执行一次,很显然我们会使用setTimeout来实现;如果是循环执行的情况,如果我们希望函数执行频率不那么高,并且间隔更稳定,通常是使用setTimeout模拟实现setInterval效果。

总的来说,虽然都被用于函数延迟执行,但两者的运行机制有本质上的区别,所以在使用的时候请注意区分。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 定时器
    +关注

    关注

    23

    文章

    3218

    浏览量

    113664
  • JS
    JS
    +关注

    关注

    0

    文章

    75

    浏览量

    17983
  • javascript
    +关注

    关注

    0

    文章

    515

    浏览量

    53656
  • 单线程
    +关注

    关注

    0

    文章

    17

    浏览量

    1757
收藏 人收藏

    评论

    相关推荐

    推荐一个支持js的嵌入式设备开发平台

    可以通过vscode开发js,实时推送js代码到设备里运行,无需编译,支持屏幕,感兴趣的可以看看 https://github.com/duoxianwulian/dxdop 提供很多js库,可以
    发表于 09-04 14:04

    【Intel Edison试用体验】16.node.js 入门setTimeout 的使用

    函数用delay是不可行的,所以我们会用到很多定时器,而在node.js中,使用setTimeout就可以完成定时器的任务,在一定时间后再次运行某函数。var mraa = require('mraa
    发表于 07-22 10:53

    【Intel Edison试用体验】[Demo] PWM-舵机 基于mraa设计高级函数库 [XDK-node.js](小k - NO.13)

    传送门:【Intel Edison试用体验】[初见] 开箱 and 测试 (小k - NO.01) 传送门:【Intel Edison试用体验】[硬件] 底层硬件探索 and 启动过程分析(小k
    发表于 08-10 17:37

    【Intel Edison试用体验】[Demo] PWM-实现LED呼吸灯,原创二次函数方法 [XDK-node.js](小k - NO.14)

    传送门:【Intel Edison试用体验】[初见] 开箱 and 测试 (小k - NO.01) 传送门:【Intel Edison试用体验】[硬件] 底层硬件探索 and 启动过程分析(小k
    发表于 08-10 23:21

    【Intel Edison试用体验】[Demo] PWM-实现蜂鸣器Bepp奏乐 [XDK-node.js](小k - NO.15)

    传送门:【Intel Edison试用体验】[初见] 开箱 and 测试 (小k - NO.01) 传送门:【Intel Edison试用体验】[硬件] 底层硬件探索 and 启动过程分析(小k
    发表于 08-10 23:49

    【Intel Edison试用体验】[Demo] ADC 光线传感器 mraa和upm [XDK-node.js](小k - NO.17)

    node.js语言,通过两种库“mraa” 和upm分别实现光学系数采集 ·工程代码,楼主自主原创,由于对node.js刚接触,不熟悉,有什么优化的地方,可以一起学习探讨。 ·准备工作 ·Edsion
    发表于 08-11 10:18

    买电脑与JS(奸商)较量六大要点

    买电脑与JS(奸商)较量六大要点 一般用户在购机与JS面对面打交道的时候,一定要牢记一条真理:JS的目的就是赚钱,不会平
    发表于 01-19 17:21 469次阅读

    node.jsjs要点总结

    Node.js是一个面向服务器的框架,立足于Chrome强大的V8 JS引擎。尽管它由C++编写而成,但是它及其应用是运行在JS上的。本文为开发者总结了4个Node.js要点。 1.
    发表于 10-13 10:39 0次下载

    鸿蒙系统中JS框架的逐行分析

    我在前文中曾经介绍过鸿蒙的 Javascript 框架,这几天终于把 JS 仓库编译通过了,期间踩了不少坑,也给鸿蒙贡献了几个 PR。今天我们就来逐行分析鸿蒙系统中的 JS 框架。 文中的所有代码都
    的头像 发表于 10-21 14:37 1893次阅读

    使用鸿蒙JS框架写出来的JS代码长什么样

    鸿蒙 JS 框架是零依赖的,只在开发打包过程中使用到了一些 npm 包。打包完的代码是没有依赖任何 npm 包的。
    的头像 发表于 03-26 15:46 2178次阅读

    Python怎么玩转JS脚本

    本项目旨在让大家了解如何用Python来执行JS脚本,其主要目的是在进行数据 分析时,需要利用爬虫获取数据,有时会遇到JS混淆加密反爬取难点,此时我们需 要获取网页JS加密代码将其
    的头像 发表于 02-23 16:26 926次阅读
    Python怎么玩转<b class='flag-5'>JS</b>脚本

    如何破解JS加密?

    学习爬虫最难之一无非就是如何破解JS加密,但是关于JS加密的网上资料非常零散杂乱,本人对这方面也略有研究,本篇文章在之前两篇文章[Python玩转JS脚本]
    的头像 发表于 02-24 14:57 1803次阅读
    如何破解<b class='flag-5'>JS</b>加密?

    简述javascript定时器工作原理

    说到 javascript 中的定时器,我们肯定会想到 setTimeout() 和 setInterval() 这两个函数。本文将从事件循环(Event Loop) 的角度来分析两者的工作原理和区别。
    的头像 发表于 04-21 14:32 704次阅读
    简述javascript定时器工作原理

    python爬虫某站JS加密逆向分析

    实现的目标:可以通过JS加密逆向后,得到加密参数,请求获取数据。此方法同样适用于被前端JS加密的用户名、密码爆破。
    的头像 发表于 05-05 15:40 1460次阅读
    python爬虫<b class='flag-5'>之</b>某站<b class='flag-5'>JS</b>加密逆向<b class='flag-5'>分析</b>

    settimeoutsetinterval有哪些区别?

    settimeoutsetinterval有哪些区别? setTimeoutsetInterval都是JavaScript中的定时器函数,用于在指定的时间间隔后执行一段代码。尽管它
    的头像 发表于 12-09 14:32 1297次阅读