Javascript引擎单线程机制及setTimeout执行原理说明
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研究室
Javascript引擎单线程机制及setTimeout执行原理说明的更多相关文章
- Javascript引擎的单线程机制和setTimeout执行原理阐述
工作中使用setTimeout解决了一个问题,于是对setTimeout的相关资料整理了下,以及对js引擎执行的原理一并整理了下,希望能给码农们一些帮助.若发现有错的地方大家及时指出,共同学习进步. ...
- 我想这次我真的理解了 JavaScript 的单线程机制
今天面试的时候被问到一个问题,是关于 JS 异步的.当时我脑海中闪过了一个单线程的概念,但却没有把真正的原理阐述清楚.所以回来特意重新回顾了前面单线程和异步相关的一些知识点. 虽然之前学习的时候也接触 ...
- Javascript:再论Javascript的单线程机制 之 DOM渲染时机
Javascript:再论Javascript的单线程机制 之 DOM渲染时机 背景 Javascript是单线程事件驱动的,所有能看到的Javascript代码都是在一个线程执行,定时器回调和AJA ...
- 我理解的javascript单线程机制
废话不多说,我们先来看几个例子: 1. setTimeout( console.log(2); result: 2 1 2. console.log(100 setTimeout( cons ...
- 简述JavaScript的运行机制
想要理解JavaScript的运行机制,需要分别深刻理解以下几个点: · JavaScript的单线程机制 · 任务队列(同步任务和异步任务) · 事件和回调函数 · 定时器 · Event Loop ...
- javascript的阻塞机制
javascript的阻塞机制 浏览器在执行javascript代码时,不能同时做其它事情,当遇到javascript时,浏览器会下载js文件,解析并执行该文件,而在这期间页面的渲染是完全被阻塞的,因 ...
- 【JS】JavaScript引擎的内部执行机制
近期在复习JavaScript,看到setTimeout函数时.想起曾经刚学时,在一本书上看过setTimeout()里的回调函数执行的间隔时间有昌不是后面设置的值.曾经没想太多.网上看了JS大 ...
- 十分钟理解JavaScript引擎的执行机制
关注专栏写文章 十分钟理解JavaScript引擎的执行机制 方伟景 千锋前端开发推动市场提升的学习研究者. 4 人赞同了该文章 首先,请牢记2点: JS是单线程语言 JS的Event Loop是JS ...
- javascript引擎执行的过程的理解--执行阶段
一.概述 同步更新sau交流学习社区(nodeJSBlog):javascript引擎执行的过程的理解--执行阶段 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍 ...
随机推荐
- C#之匿名类型与隐式局部变量
一.匿名类型 下面一段代码展示了如何定义并且使用匿名类型: static void Main(string[] args) { var patent1 = new { Title = "Ne ...
- Linux平台下:块设备、裸设备、ASMlib、Udev相关关系
对磁盘设备(裸分区)的访问方式分为两种:1.字符方式访问(裸设备):2.块方式访问 Solaris平台 : 在Solaris平台下,系统同时提供对磁盘设备的字符.块方式访问.每个磁盘有两个设备文件名: ...
- IEnumerable 接口 实现foreach 遍历 实例
额 为啥写着东西? 有次面试去,因为用到的时候特别少 所以没记住, 这个单词 怎么写! 经典的面试题: 能用foreach遍历访问的对象的要求? 答: 该类实现IEnumetable 接口 声明 ...
- [Prism框架实用分享]如何在主程序中合理的弹出子窗体
大家好 说起子窗体,大家都会想到ChildWindow,多熟悉的一个控件.不错,Sliverlight中已经提供了子窗体的具体实现,而在WPF中却没有这么好的事情(有的第三方控件商已经提供此控件).最 ...
- windows下找不到strings.h
头文件用的strings.h,换成string.h就好了.但是以前的Linux系统下用strings.h,strerror都能正常编译,怎么样能正常使用strings.h linux系统下的库问题跟w ...
- 使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史
构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件,无论它们是内部请求还是网络连接,都要有效地处理它们的操作. 有许多解决方案,但事件驱动也被广泛应用到网络编程中.并大规模部署在高 ...
- 算法系列6《MAC》
1. 简介 MAC是使用命令的所有元素(包括命令头)产生的.一条命令的完整性,包括命令数据域(如果存在的话)中的数据元,通过安全报文传送得以保证.按照如下的方式使用单重或三重DEA加密方式产生MAC: ...
- 同时执行2个存储过程,2个SP中分别有相同的临时表名,会有冲突吗?
同时执行2个存储过程,2个SP中分别有相同的临时表名,会有冲突吗?答案:不会 这就可以在以后写存储过程的时候统一临时表名了. alter procedure sp_01 as begin create ...
- shelll函数求两个输入数字之和
#!/bin/bash #This is a test of the addition of the program! function AddFun { read -p "Enter a ...
- oracle 查询谁在用undo
SELECT TO_CHAR(s.sid)||','||TO_CHAR(s.serial#) sid_serial,NVL(s.username, 'None') orauser,s.program, ...