javascript中的定时器(How JavaScript Timers Work)

javascript定时器工作原理是一个重要的基础知识点。因为定时器在单线程中工作,它们表现出的行为很直观。我们该如何创建和维护定时器呢?要从如下三个函数(都是定义在全局作用域,在浏览器中就是window的方法)说起:

  • var id=setTimeout(fn,delay);-初始化一个只执行一次的定时器,这个定时器会在指定的时间延迟delay之后调用函数fn,该setTimeout函数返回定时器的唯一id,我们可以通过这个id来取消定时器的执行。
  • var id=setInvertal(fn,delay);-与setTimeout类似,只是它会以delay为周期,反复调用函数fn,直到我们通过id取消该定时器。
  • clearInterval(id),clearTimeout(id),;-这两个函数接受定时器的id(例如我们上面提到的两个函数产生的定时器id),并停止对定时器中指定函数的调用。

要深入理解定时器工作原理,我们需要探索一个重要的概念:定时器指定的延迟时间并不能得到保证。在浏览器中,因为所有的javascript代码都运行在单一线程之中,异步事件(如鼠标点击,定时器)只有在他们被触发的时候他们的回调才有机会得以执行。我们可以用下图说明:

查看大图

图中包含大量的信息,吸收并理解这些信息,能帮助我们领悟“异步的javascript代码是如何工作的”。这个图是一维的,垂直方向是时间,以毫秒为单位。蓝色的盒子代表正在执行的javascript代码所占时间片段。例如 第一个javascript块执行时间约18ms,第二个鼠标点击块执行了约11ms,其他块类似。

因为单线程的缘故,在同一时间只能执行一条javascript代码,每一个代码块(蓝色盒子)都会阻塞其他异步事件的执行。这就意味着,当一个异步事件发生的时候(例如鼠标点击,定时器触发,一个XMLHttpRequest 请求完成),它进入了代码的执行队列,执行线程空闲时会依照该执行队列中顺序依次执行代码。(如何将异步事件加入队列,不同浏览器,他们的实现可能有所差异,所以这里我们将其简单化)。

开始的时候,在Javascript代码块(第一个盒子),初始化了两个定时器,一个10ms延迟的setTimeout 和10ms的setInterval。这些定时器可能会在我们第一个代码块执行结束之前就触发,这取决于定时器在第一个代码块中启动的位置和时间。注意,定时器虽然触发了,但是并不会立即执行,它只是把需要延迟执行的函数加入了执行队列,在线程的某一个可用的时间点,这个函数就能够得到执行。

当第一个Javascript代码初始化块执行结束,浏览器立即提出一个问题:谁在等待着被执行? 在这个案例中鼠标点击时间的处理程序和一个定时器(setTimeout)都在等待。浏览器选择一个并执行(这里是鼠标点击事件的处理程序)。定时器就需要等待下一个可用时间来执行。

需要注意的是当鼠标点击事件处理程序执行的时候,第一个interval定时器触发了。和timeout定时器一样,他的回调函数被加入了执行队列,等待执行。然而,还需要注意到当interval定时器再次触发,这个时候timeout定时器的回调函数正在执行,此时这个interval的触发被放弃了。假想(浏览器不这样做),在一个占用时间很多的初始化定时器的代码块中,所有的interval触发都把回调加入执行队列,当初始化代码块结束后,执行队列中已经累加了大量的定时器回调函数,结果就会出现大量的interval回调函数无间隔的执行,直到该执行队列清空。所以浏览器在讲一个interval回调加入执行队列前,会检查执行队列,如果其中存在尚未执行的interval回调那么就等待,直到当前执行队列中没有相应interval的回调以后才会继续入队interval回调。

事实上,如图,我们看见在第一个Interval的回调执行的时候(之前进入执行队列),第三个interval触发了,这想我们展示一个重要的现象:Interval不关心当前正在执行的代码,他们会不加选择的添加回调到执行队列,尽管这意味着两个interval回调函数执行的时间间隔被牺牲。这里第一个interval回调执行结束后,紧跟着第三个interval的回调马上得到执行,中间没有印象中应该有的10ms间隔。

最终,在第三个interval的回调执行结束后,我们看见执行队列中没有等待javascript引擎执行的代码,这就意味着,浏览器现在等待新的异步事件的发生,在50ms的刻度处interval再次触发,此时没有什么会阻塞javascript引擎,这个interval回调会立即执行。

让我们看一个例子来阐明,setInterval和setTimeout的不同,

setTimeout(function(){
    /* Some long block of code... */
    setTimeout(arguments.callee, 10);
  }, 10);
 
  setInterval(function(){
    /* Some long block of code... */
  }, 10);

看第一眼,会觉得这两段代码功能相同,实际上,他们是不同的。

需要注意到,setTimeout的回调函数的执行总是保证了至少10ms的间隔(与上一个回调的执行相比,实际执行时,这个间隔可能变长,但是不可能更少),但是setInterval会尝试每隔10ms执行一次回调,不管上一个回调函数时候已经执行完毕。(很多类库的动画都是使用的setTimeout实现)

这里我们学到很多,总结一下:

  • javascript引擎是单线程的,会迫使异步事件进入执行队列,等待执行。
  • setTimeout和setInterval在执行异步代码时从根本上是有所不同的。
  • 如果一个定时器事件被阻塞,使得它不能立即执行,那么它会被延迟,直到下一个可能的时间点,才被执行(这可能比你指定的delay时间要长)
  • Interval的回调有可能‘背靠背’无间隔的执行,这种情况是说interval的回调函数的执行时间比你指定的delay时间还要长

这些都是构建javascript应用程序非常重要的知识。了解javascript Engine是如何工作的,特别存在大量的异步事件发生,为构建高级应用程序代码打下基础。

javascript中的定时器(How JavaScript Timers Work)

时间: 2024-11-01 01:29:41

javascript中的定时器(How JavaScript Timers Work)的相关文章

[ Javascript ] JavaScript中的定时器(Timer) 是如何工作的!

作为入门者来说,了解JavaScript中timer的工作方式是很重要的.通常它们的表现行为并不是那么地直观,而这是因为它们都处在一个单一线程中.让我们先来看一看三个用来创建以及操作timer的函数. var id = setTimeout(fn, delay); - 初始化一个单一的timer,这个timer将会在一定延时后去调用指定的函数.这个函数(setTimeout)将返回一个唯一的ID,我们可以通过这个ID来取消timer. var id = setInterval(fn, delay

JavaScript中的定时器

定时器 1.setTimeout 这个方法用于在指定的毫秒数之后执行某个函数,返回定时器的句柄 混合的 setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码. 语法 let timeoutID = window.setTimeout(func[, delay, param1, param2, ...]); let timeoutID = scope.setTimeout(code[, delay]); let timeoutID = window.set

JavaScript事件驱动机制&定时器机制

在浏览器中,事件作为一个极为重要的机制,给予JavaScript响应用户操作与DOM变化的能力:在NodeJS中,异步事件驱动模型则是提高并发能力的 基础. 一.程序如何响应事件 程序响应外部的事件有两种方式: 1. 中断 操作系统处理键盘等硬件输入就是通过中断来进行的,这个方式的好处是即使没有多线程,我们也可以放心地执行我们的代码,CPU收到中断信号之后 自动地转去执行相应的中断处理程序,处理完成后会恢复原来的代码的执行环境继续执行.这种方式需要硬件的支持,一般来说都会被操作系统封装起 来.

JavaScript中的内存泄漏以及如何处理

随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概述 像C语言这样的编程语言,具有简单的内存管理功能函数,例如malloc( )和free( ).开发人员可以使用这些功能函数来显式地分配和释放系统的内存. 当创建对象和字符串等时,JavaScript就会分配内存,并在不再使用时自动释放内存,这种机制被称为垃圾收集.这种释放资源看似是"自动"

理解 JavaScript 中的 this

前言 理解this是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this,你才能更加清晰地写出与自己预期一致的 JavaScript 代码. 本文是这系列的第三篇,往期文章: 理解 JavaScript 中的作用域 理解 JavaScript 中的闭包 什么是 this 消除误解 在解释什么是this之前,需要先纠正大部分人对this的误解,常见的误解有: 指向函数自身. 指向它所在的作用域. 关于为何会误解的原因这里不多讲,这里只给出结论,有兴趣可以自行查询资料

JavaScript中的正则表达式(终结篇)

JavaScript中的正则表达式(终结篇) 在之前的几篇文章中,我们了解了正则表达式的基本语法,但那些语法不是针对于某一个特定语言的.这篇博文我们将通过下面几个部分来了解正则表达式在JavaScript中的使用: JavaScript对正则表达式的支持程度 支持正则表达式的RegExp类型 RegExp的实例属性 RegExp的实例方法 RegExp的构造函数属性 简单的应用 第一部分:JavaScript对正则表达式的支持程度 之前我介绍了正则表达式的基本语法,如果大家不是很了解可以先看下面

在 javascript 中,为什么 [1,2] + [3,4] 不等于 [1,2,3,4]?

问题 我想将一个数组追加到另一个数组的后面,于是我在 firebug 编写如下代码: [1,2] + [3,4] 但是,出乎意料,它却输出了: "1,23,4" 而没有输出我期望的: [1,2,3,4] 解答 JavaScript 的 + 运算符有两个目的: 将两个数相加: 将两个字符串连接. 规范并没有定义 + 运算符在数组上的行为,所以javascript 首先 把数组转换成字符串,然后在字符串上进行 + 运算. 如果想连接两个数组,可以使用数组的 concat 方法: [1, 2

javascript中原型(prototype)与原型链

javascript是一门动态语言(动态语言Dynamic Programming Language:动态类型语言,意思就是类型的检查是在运行时做的,也就是常说的“弱类型”语言),没有类的概念,有class保留字,但不能用作变量名 原型:Javascript中的每一个对象都有一个内部私有的连接指向另一个对象,这个对象就是原对象的原型 注意:原型是一个对象,其他对象可以通过他实现属性继承 原型链:这个原型对象也有自己的原型,直到对象的原型为null为止(也就是没有原型),这种一级一级的链结构就称为

JavaScript中判断为整数的多种方式

原文:JavaScript中判断为整数的多种方式 之前记录过JavaScript中判断为数字类型的多种方式,这篇看看如何判断为整数类型(Integer). JavaScript中不区分整数和浮点数,所有数字内部都采用64位浮点格式表示.但实际操作中比如数组索引.位操作则是基于32位整数. 方式一.使用取余运算符判断 任何整数都会被1整除,即余数是0.利用这个规则来判断是否是整数. function isInteger(obj) { return obj%1 === 0 } isInteger(3