setTimeout浅析
刚学习javascript的时候,感觉setTimeout很好理解,不就是过n(传入的毫秒数)毫秒,执行以下传入的函数吗?这个理解伴随了我挺长的一段时间,才对setTimeout有了新的认识,请先看下面的例子:
var start = new Date()
setTimeout(function(){
var end = new Date()
console.log("时间间隔:", end - start, "ms")
}, 500)
while( new Date() - start < 1000 ){}
如果是刚开始学习javascript的我可能会得到500ms的结果,真实的结果大约是一个1010ms的结果,为什么会这样那?
要能清楚这个问题,要先知道浏览器是怎样运行javascript的。在大多数浏览器中,执行的javascript代码和用户的UI界面更新是共用一个线程的,它的工作原理是这样的:与线程对应着一个简单的队列,每当执行的javascript代码或更新用户的UI界面时,处理任务会先进入等待队列,当线程空闲时,最先进入队列的任务就会被提取出来运行。如果我现在点击一个网页中的 button按钮,并且点击按钮触发一个click事件。代码如下:
document.getElementById("btn").onclick = function(){
dosomething()
var div = document.createElement("div")
div.innerHTML = "test"
document.body.appendChild(div)
}
浏览器对刚才操作的处理大致如下图:

当点击button按钮时,浏览器会创建两个任务加入到线程的队列中。第一个任务是更新按钮的样式,让用户知道按钮被点击了;第二个任务是执行click事件触发对应的javascript代码。假设这个时候线程是空闲状态,那么第一个任务就会被提取出来并执行,然后第二个任务就会被提取出来运行,在执行过程中,javascript代码创建了一个新的div元素,并追加到body元素的后面,这其实引发了另一次UI的变化。这意味着,在javascript代码运行过程中,一个新的UI更新任务被加入到队列中,当第二个任务完成后,UI还会再更新一次。
知道了UI线程的工作原理,再回头看上面的例子就不难理解,函数不是在500ms的时候立即执行,而是在500ms的时候加入等待队列。加入等待队列后发现现在线程不是空闲的,因为while在500ms的时候还在执行,当while执行结束后,也就是1010ms左右以后,线程才空闲。所以最后的结果是1010ms左右。如图:

所以类似如下代码返回什么值就很好理解了
for(var i =1; i<=3; i++ ){
setTimeout(function(){
console.log( i )
},0)
}
与setTineout类似的还有setInterval,但是setInterval会重复添加javascript任务到队列。当队列中已存在同一个setInterval创建的任务时,后续任务就不会添加到队列中。基于setInterval的设计,会导致两个问题:一个是某些间隔会被跳过;一个是多个定时器的代码执行间隔会比预期的小。看一下下面这段代码:
document.getElementById("btn").onclick = function(){
var start = new Date()
console.log("开始...")
setInterval( function(){
var start = new Date()
console.log("interval begin")
while( new Date() - start < 3200 ){}
console.log( "interval end")
}, 2000 )
while( new Date() - start < 3200 ){}
}
点击一个button按钮,执行上面的代码,设置了一个200ms间隔的重复定时器,click事件处理程序大约运行了3200ms,定时器代码也大约运行了3200ms。分析如图:

第一个定时器是在2000ms左右加入到队列的,但是这个时候click事件处理程序还没有运行完,等到3200ms左右这个时刻,click事件处理程序运行完,在队列中的第一个定时器任务会进入线程运行。在4000ms左右的时间,第二个定时器任务会进入等待队列。这个时候第一个定时器代码还在运行,并且在6000ms左右的时间第一个定时器代码还是在运行的。这样第二个定时器任务还在等待队列中,所以在6000ms左右,添加不了第三个定时器任务到等待队列。更有问题的是当第一个定时器任务运行结束后,第二个定时器的代码会立即执行。
为了避免setInterval的问题,聪明的前人想到了用setTimeout模拟setInterval。上面的代码可以改写成:
document.getElementById("btn").onclick = function(){
var start = new Date()
console.log("开始...")
setTimeout( function(){
var start = new Date()
console.log("interval begin")
while( new Date() - start < 3200 ){}
console.log( "interval end")
setTimeout(arguments.callee, 2000)
}, 2000 )
while( new Date() - start < 3200 ){}
}
这样做的好处是在本次定时代码执行完以前,不会向等待队列中添加新的定时器。这样在下一次定时器代码执行之前,至少会等待指定的时间间隔。同时因为是setTimeout模拟也不会缺失。
setTimeout浅析的更多相关文章
- setTimeout 与 Event Loop 浅析
先从一个小题目开始: 以下代码的输出结果是? function test1 () { console.log(1) }; setTimeout(test1, 1000); // T1-1setTime ...
- setTimeout与setInterval的区别浅析
在网页制作动态效果时,一定会遇到某些需求,要求某段程序等待多时时间后再开始执行,就像在我们的生活中一样,待会儿再开始做一件事.在JavaScript中主要通过定时器实现此类需求,本文将对定时器做一个概 ...
- 【深入浅出jQuery】源码浅析2--奇技淫巧
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 【深入浅出jQuery】源码浅析2--使用技巧
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 浅析Node.js的Event Loop
目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...
- Android WebView File域同源策略绕过漏洞浅析
0x00 我们首先讲一个webView这种方法的作用: webView.getSettings().setAllowFileAccessFromFileURLs(false); ...
- Nodejs 基础知识 浅析
1. 模块化 ①常用模块化规范 CommonJS + nodejs AMD(Asynchronous Module Definition) + RequireJS CMD(Common Module ...
- 分布式锁----浅析redis实现
引言大概两个月前小伙伴问我有没有基于redis实现过分布式锁,之前看redis的时候知道有一个RedLock算法可以实现分布式锁,我接触的分布式项目要么是github上开源学习的,要么是小伙伴们公司项 ...
- Vue.nextTick浅析
Vue.nextTick浅析 Vue的特点之一就是响应式,但数据更新时,DOM并不会立即更新.当我们有一个业务场景,需要在DOM更新之后再执行一段代码时,可以借助nextTick实现.以下是来自官方文 ...
随机推荐
- 【转】Android 布局学习之——LinearLayout属性baselineAligned的作用及baseline
相信大家对LinearLayout已经相当熟悉,但你们是否了解它的属性baselineAligned呢? Android官方文档是这么描述的:
- python学习day4--python基础--字典
字典的常用操作: #字典天然去重,key唯一,如果key相同,只能打印出一个 id_db={ 220456789852963741:{ 'name':"alex", 'age':3 ...
- [转]WCF 4 安全性和 WIF 简介
转自:http://www.cnblogs.com/WizardWu/archive/2010/10/04/1841793.html 本帖简介 .NET 新一代的 Windows Identity ...
- Objective-C 【内存管理&手动内存管理 综述】
------------------------------------------- 内存管理 (1)Objective-C的内存管理 栈区 存放局部变量(由于基本数据类型占用的存储空间是固定 ...
- css Hack 以及css的一些兼容问题小结
坚持每天做总结.今天下班还算早.写个跟css兼容有关的知识点.便于后期查看与学习.一.先说说各种主流浏览器的内核 浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“渲染引 ...
- Fuck Flyme Theme
转载说明 本篇文章可能已经更新,最新文章请转:http://www.sollyu.com/fuck-flyme-theme/ 说明 本插件仅用于魅蓝Note, MX3, MX4, MX4 PRO它机型 ...
- Linux--fedora21 PC机安装以及拨号上网和无限上网
最近回家,学习许久未用的linux,之前也是在培训的时候用的是 ubuntu ,这次回家查了下 fedora 最适合开发人员使用,所以就装了个试试.刚开始只能拨号上网,经过三天时间的各种搜索查找终于解 ...
- javascript之高级函数应用思想
1.级联函数:应用对象方法调用的连写 function A(){ this.a = ''; this.b = ''; this.c = ''; } //改造一下 A.prototype = { A.p ...
- Ubuntu的默认root密码
Ubuntu的默认root密码是随机的,即每次开机都有一个新的root密码.我们可以在终端输入命令 sudo passwd,然后输入当前用户的密码,enter,终端会提示我们输入新的密码并确认,此时的 ...
- RHEL 6.5升级GCC 4.9.3
前提:保证旧版的gcc,g++存在! root用户 1. 下载源码和依赖包源码:新建目录bakwget http://ftp.gnu.org/gnu/gcc/gcc-4.9.3/gcc-4.9.3.t ...