setTimeout与setInterval原理分析
一、概述
setTimeout与setInterval是JavaScript引擎提供的两个定时器方法,分别用于函数的延时执行和循环调用。setTimeout的主要思想是通过一个定时器,让函数在计时结束后再执行;setInterval则是每隔一定的时间,就启动一次函数的执行。
二、基本原理
1. 单线程模型
- 定义:JavaScript被设计为用在浏览器环境,为避免复杂的冲突处理,JavaScript的设计者将其设计成了一门单线程语言(执行引擎在同一时间只做一件事)。
- 注意:这里的单线程是指JavaScript的主线程只有一个。除了这个主线程,JavaScript还有一个I/O线程,但两者之间相对独立,不需要进行状态同步。
2. 任务队列
- 定义:任务队列用于存储等待执行的任务。如果当前有一个任务需要执行,但JavaScript引擎正在执行其他任务,那么这个任务就需要放进一个队列中进行等待。
- 执行机制:等到线程空闲时,从队列中取出最早加入的任务进行执行。如果当前线程空闲,并且队列为空,那每次加入队列的函数将立即执行。
3. setTimeout与setInterval
- setTimeout(func, delay, args):设置超时调用。JavaScript引擎会为func函数设置一个计时器,delay毫秒后,将func添加到任务队列等待执行。
- setInterval(func, interval, args):设置循环调用。JavaScript引擎每隔interval毫秒就会把func添加到任务队列一次。
相同点:
- 两者都会加入同一个队列,等待线程空闲时执行。
- 两者都无法保证在何时执行回调,因为无法知道线程何时空闲。
不同点:
- setTimeout只会将函数添加到任务队列一次,而setInterval则是循环往队列中添加函数。
- setTimeout可以保证函数在指定的时间间隔内不会执行,而setInterval无法保证(有可能出现接近连续执行的情况)。
三、运行机制
1. setTimeout
- 运行机制:在执行该语句时,设置一个定时器,定时时间置为所设置的延时。当计时结束后,将传入的函数加入任务队列,之后的执行就交给任务队列负责。
- 取消执行:setTimeout函数本身会返回一个句柄,可以在函数执行前通过clearTimeout传入该句柄取消函数的执行。
2. setInterval
- 运行机制:每隔一定的时间向任务队列添加回调函数。但如果在向队列中添加回调函数时,队列中存在之前由其添加的回调函数,就放弃本次添加(不会影响之后的计时)。
- 取消执行:可以通过clearInterval方法移除定时器,使用方法同clearTimeout。
性能问题:
- 如果函数执行时间较长,setInterval可能会出现连续执行的情况,因为setInterval只负责定时向队列中添加函数,而不考虑函数的执行。
- 在很多情况下,setInterval会遇到性能问题,因此实际编码中,开发者通常会使用setTimeout来模拟实现setInterval效果。
四、应用场景
1. setTimeout
- 主要用于需要进行延时调用的场景中。
- 由于setInterval存在的性能问题,开发人员通常会使用setTimeout来模拟setInterval,以防止出现函数连续执行的情况。
2. setInterval
- 使用场景相对较少,但当所使用的接口来自外部(即回调函数本身无法修改)时,就必须通过setInterval来实现循环执行。
- 对于动画效果来说,使用setInterval往往更加流畅。
五、补充说明
- setTimeout与setInterval的第一个参数可以是一个匿名函数,也可以是一个函数名,或者是一个字符串(但字符串形式不推荐使用,因为可读性和可维护性差)。
- 当给setTimeout传入的延迟时间为0时,并不代表回调函数会立即执行。实际上,浏览器有一个默认的最短计时时间,一般为4毫秒(老版本的浏览器则会更长一些)。
六、总结
setTimeout与setInterval都是通过一个定时器控制回调函数的执行,但由于JavaScript单线程的特点,两者都不能准确控制函数的执行时间点。如果函数只需要执行一次,使用setTimeout;如果是循环执行的情况,并且希望函数执行频率不那么高、间隔更稳定,通常使用setTimeout模拟实现setInterval效果。总的来说,虽然都被用于函数延迟执行,但两者的运行机制有本质上的区别,在使用时请注意区分。
(以上图片用于辅助理解setTimeout与setInterval的运行机制)