setTimeout用法在实际项目中还是会时常遇到。比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何改变而忽略这两句话,因此我们可以通过setTimeout把“设回白色”函数加入下一个堆栈,那么就可以确保背景颜色发生过改变了(虽然速度很快可能无法被察觉)。

  总之,setTimeout增加了Javascript函数调用的灵活性,为函数执行顺序的调度提供极大便利。

然后,我们从基础的层面来看看:理解JavaScript的定时器是如何工作的是非常重要的。

计时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的。我们先来认识一下下面三个函数是如何控制计时器的。

var id = setTimeout(fn, delay); - 初始化一个计时器,然后在指定的时间间隔后执行。该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器。

var id = setInterval(fn, delay); - 和setTimeout有些类似,但它是连续调用一个函数(时间间隔是delay参数)直到它被取消。

clearInterval(id);, clearTimeout(id); - 使用计时器ID(setTimeout 和 setInterval的返回值)来取消计时器回调的发生

有一个基本的概念你得记住了:

时间延迟不能被保证。什么意思,就是说你这样写setTimeout(fn, 500)并不代表fn肯定在500毫秒之后马上就执行,延迟很可能会更长。因为 JavaScript 是单线程语言,所有的异步事件(包括计时器、鼠标事件或者一个 XMLHttpRequest 完成)仅仅当程序执行期间有缺口的时候才会执行,不是你规定了什么时候就什么时候执行,要知道程序员不是万能的,你写的东西最终还是要看浏览器脸色的。

用一个很好的图表加以说明:

JavaScript引擎用单线程运行也是有意义的,单线程不必理会线程同步这些复杂的问题,问题得到简化.

那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
下面结合浏览器内核处理方式简单说明.

浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步.假如某一浏览器内核的实现至少有三个常驻线 程:javascript引擎线程,界面渲染线程,浏览器事件触发线程,除些以外,也有一些执行完就终止的线程,如Http请求线程,这些异步线程都会产 生不同的异步事件,下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的.虽然每个浏览器内核实现细节不同,但这其中的 调用原理都是大同小异.

由图可看出,浏览器中的JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可以源自 JavaScript引擎当前执行的代码块,如调用setTimeout添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发 器时间到达通知,异步请求状态变更通知等.从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线 程关系,这些任务得进行排队,一个接着一个被引擎处理.

IE8及其之前的IE版本更新间隔为15.6毫秒。假设你设定的setTimeout延迟为16.7ms,那么它要更新两个15.6毫秒才会该触发延时。这也意味着无故延迟了 15.6 x 2 - 16.7 = 14.5毫秒。

16.7ms
DELAY: |------------|

CLOCK: |----------|----------|
         15.6ms    15.6ms

所以即使你给setTimeout设定的延时为0ms,它也不会立即触发。目前Chrome与IE9+浏览器的更新频率都为4ms(如果你使用的是笔记本电脑,并且在使用电池而非电源的模式下,为了节省资源,浏览器会将更新频率切换至于系统时间相同,也就意味着更新频率更低)。

退一步说,假使timer resolution能够达到16.7ms,它还要面临一个异步队列的问题。因为异步的关系setTimeout中的回调函数并非立即执行,而是需要加入等待队列中。但问题是,如果在等待延迟触发的过程中,有新的同步脚本需要执行,那么同步脚本不会排在timer的回调之后,而是立即执行

让我们用一个例子来阐明setTimeout和setInterval之间的区别:

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

这两句代码乍一看没什么差别,但是它们是不同的。setTimeout回调函数的执行和上一次执行之间的间隔至少有10ms(可能会更多,但不会少于10ms),而setInterval的回调函数将尝试每隔10ms执行一次,不论上次是否执行完毕。

在这里我们学到了很多知识,总结一下:

JavaScript引擎是单线程的,强制所有的异步事件排队等待执行

setTimeout 和 setInterval 在执行异步代码的时候有着根本的不同

如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些)

如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

参考:

一家之言:说说 JavaScript 计时器的工作原理_大前端

http://www.daqianduan.com/1112.html

JavaScript的单线程性质以及定时器的工作原理 - Rain man - PHP博客

http://www.phpweblog.net/rainman/archive/2009/01/05/6267.html

JavaScript可否多线程? 深入理解JavaScript定时机制 - 前端交互 - PHP5研究室

http://www.phpv.net/html/1700.html 

 

Javascript引擎单线程机制及setTimeout执行原理说明的更多相关文章

  1. Javascript引擎的单线程机制和setTimeout执行原理阐述

    工作中使用setTimeout解决了一个问题,于是对setTimeout的相关资料整理了下,以及对js引擎执行的原理一并整理了下,希望能给码农们一些帮助.若发现有错的地方大家及时指出,共同学习进步. ...

  2. 我想这次我真的理解了 JavaScript 的单线程机制

    今天面试的时候被问到一个问题,是关于 JS 异步的.当时我脑海中闪过了一个单线程的概念,但却没有把真正的原理阐述清楚.所以回来特意重新回顾了前面单线程和异步相关的一些知识点. 虽然之前学习的时候也接触 ...

  3. Javascript:再论Javascript的单线程机制 之 DOM渲染时机

    Javascript:再论Javascript的单线程机制 之 DOM渲染时机 背景 Javascript是单线程事件驱动的,所有能看到的Javascript代码都是在一个线程执行,定时器回调和AJA ...

  4. 我理解的javascript单线程机制

    废话不多说,我们先来看几个例子: 1. setTimeout( console.log(2); result:  2 1   2.   console.log(100 setTimeout( cons ...

  5. 简述JavaScript的运行机制

    想要理解JavaScript的运行机制,需要分别深刻理解以下几个点: · JavaScript的单线程机制 · 任务队列(同步任务和异步任务) · 事件和回调函数 · 定时器 · Event Loop ...

  6. javascript的阻塞机制

    javascript的阻塞机制 浏览器在执行javascript代码时,不能同时做其它事情,当遇到javascript时,浏览器会下载js文件,解析并执行该文件,而在这期间页面的渲染是完全被阻塞的,因 ...

  7. 【JS】JavaScript引擎的内部执行机制

     近期在复习JavaScript,看到setTimeout函数时.想起曾经刚学时,在一本书上看过setTimeout()里的回调函数执行的间隔时间有昌不是后面设置的值.曾经没想太多.网上看了JS大 ...

  8. 十分钟理解JavaScript引擎的执行机制

    关注专栏写文章 十分钟理解JavaScript引擎的执行机制 方伟景 千锋前端开发推动市场提升的学习研究者. 4 人赞同了该文章 首先,请牢记2点: JS是单线程语言 JS的Event Loop是JS ...

  9. javascript引擎执行的过程的理解--执行阶段

    一.概述 同步更新sau交流学习社区(nodeJSBlog):javascript引擎执行的过程的理解--执行阶段 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍 ...

随机推荐

  1. C#之匿名类型与隐式局部变量

    一.匿名类型 下面一段代码展示了如何定义并且使用匿名类型: static void Main(string[] args) { var patent1 = new { Title = "Ne ...

  2. Linux平台下:块设备、裸设备、ASMlib、Udev相关关系

    对磁盘设备(裸分区)的访问方式分为两种:1.字符方式访问(裸设备):2.块方式访问 Solaris平台 : 在Solaris平台下,系统同时提供对磁盘设备的字符.块方式访问.每个磁盘有两个设备文件名: ...

  3. IEnumerable 接口 实现foreach 遍历 实例

    额 为啥写着东西? 有次面试去,因为用到的时候特别少 所以没记住, 这个单词 怎么写! 经典的面试题: 能用foreach遍历访问的对象的要求? 答:  该类实现IEnumetable 接口   声明 ...

  4. [Prism框架实用分享]如何在主程序中合理的弹出子窗体

    大家好 说起子窗体,大家都会想到ChildWindow,多熟悉的一个控件.不错,Sliverlight中已经提供了子窗体的具体实现,而在WPF中却没有这么好的事情(有的第三方控件商已经提供此控件).最 ...

  5. windows下找不到strings.h

    头文件用的strings.h,换成string.h就好了.但是以前的Linux系统下用strings.h,strerror都能正常编译,怎么样能正常使用strings.h linux系统下的库问题跟w ...

  6. 使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史

    构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件,无论它们是内部请求还是网络连接,都要有效地处理它们的操作. 有许多解决方案,但事件驱动也被广泛应用到网络编程中.并大规模部署在高 ...

  7. 算法系列6《MAC》

    1. 简介 MAC是使用命令的所有元素(包括命令头)产生的.一条命令的完整性,包括命令数据域(如果存在的话)中的数据元,通过安全报文传送得以保证.按照如下的方式使用单重或三重DEA加密方式产生MAC: ...

  8. 同时执行2个存储过程,2个SP中分别有相同的临时表名,会有冲突吗?

    同时执行2个存储过程,2个SP中分别有相同的临时表名,会有冲突吗?答案:不会 这就可以在以后写存储过程的时候统一临时表名了. alter procedure sp_01 as begin create ...

  9. shelll函数求两个输入数字之和

    #!/bin/bash #This is a test of the addition of the program! function AddFun { read -p "Enter a ...

  10. oracle 查询谁在用undo

    SELECT TO_CHAR(s.sid)||','||TO_CHAR(s.serial#) sid_serial,NVL(s.username, 'None') orauser,s.program, ...