刚学习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浅析的更多相关文章

  1. setTimeout 与 Event Loop 浅析

    先从一个小题目开始: 以下代码的输出结果是? function test1 () { console.log(1) }; setTimeout(test1, 1000); // T1-1setTime ...

  2. setTimeout与setInterval的区别浅析

    在网页制作动态效果时,一定会遇到某些需求,要求某段程序等待多时时间后再开始执行,就像在我们的生活中一样,待会儿再开始做一件事.在JavaScript中主要通过定时器实现此类需求,本文将对定时器做一个概 ...

  3. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  4. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  6. Android WebView File域同源策略绕过漏洞浅析

       0x00     我们首先讲一个webView这种方法的作用: webView.getSettings().setAllowFileAccessFromFileURLs(false);     ...

  7. Nodejs 基础知识 浅析

    1. 模块化 ①常用模块化规范 CommonJS + nodejs AMD(Asynchronous Module Definition) + RequireJS CMD(Common Module ...

  8. 分布式锁----浅析redis实现

    引言大概两个月前小伙伴问我有没有基于redis实现过分布式锁,之前看redis的时候知道有一个RedLock算法可以实现分布式锁,我接触的分布式项目要么是github上开源学习的,要么是小伙伴们公司项 ...

  9. Vue.nextTick浅析

    Vue.nextTick浅析 Vue的特点之一就是响应式,但数据更新时,DOM并不会立即更新.当我们有一个业务场景,需要在DOM更新之后再执行一段代码时,可以借助nextTick实现.以下是来自官方文 ...

随机推荐

  1. SQLite CRUD操作

    SQLite CRUD操作代码实例: 1:首先创建一个继承了SQLiteOpenHelper类的MyDatabaseHelper类.实现他的onCreate(SQLiteDatabase db) on ...

  2. 相对完美的后台Service实现播放音乐功能

    对于用Context.startService()启动的service生命周期为onCreate()-onStartCommand()-onDestroy();如果多次用context.startSe ...

  3. 【转】Github入门教程

    原文 http://www.eoeandroid.com/thread-274556-1-1.html [初识Github] 首先让我们大家一起喊一句“Hello Github”.YEAH!就是这样. ...

  4. xenserver 备份backup和还原restore命令

      转载:http://zhumeng8337797.blog.163.com/blog/static/100768914201425103713738/ 1. 备份和还原pool中的metadata ...

  5. C# 线程--第四线程实例

    概述 在前面几节中和大家分享了线程的一些基础使用方法,本章结合之前的分享来编写一些日常开发中应用实例,和编写多线程时一些注意点.如大家有好的实例也欢迎分享.. 应用实例 应用:定时任务程序 场景:系统 ...

  6. 引用类型之Function类型

    Function类型 ECMAScript中最有意思的就是函数了,有意思的根源,在于函数实际上是对象.每个函数都是Function的实例,具有属性和方法.而重要的一点是,函数名,不过是指向函数的指针, ...

  7. Session 的配置和特性

    session的配置 对于session的配置是php.ini中配置 session数据都是保存在文本文件中 设置session文件的保存位置 说明:     默认是保存在windows/temp目录 ...

  8. feature.shape和feature.shapecopy的区别

    以前在写AE代码的时候也没有注意到feature.shape和feature.shapecopy的区别,觉得两者也差不多: 今天写入库程序才明白过来. 如果取feature.shape,则得到的是该要 ...

  9. 腾讯QQ表情为什么如此成功呢

    本人为原创作品:e良师益友 ,转载是并且注明 e良师益友网导读:腾讯开发的QQ表情功能给中国人的聊天增添一抹幽默,很多时候图片表情比话语更好的表达我们的意思,翻开你的聊天记录就会发现夹杂这很多不同的表 ...

  10. L006-oldboy-mysql-dba-lesson06

    L006-oldboy-mysql-dba-lesson06 数据清理状态,先标记update table state=1,再删除. myisam没外键,硬件,并发,锁表力度,不支持事务,OLAP. ...