从setTimeout说起

  众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执行完才能有机会执行,不像人一样,人是多线程的,所以你可以一边观看某岛国动作片,一边尽情挥洒汗水。JavaScript单线程机制也是迫不得已,假设有多个线程,同时修改某个dom元素,那么到底是听哪个线程的呢?

  既然已经明确JavaScript是单线程的语言,于是我们想方设法要想出JavaScript的异步方案也就可以理解了。比如执行到某段代码,需求是1000ms后调用方法A,JavaScript没有sleep函数能挂起线程一秒啊?如何能够使得代码做到一边等待A方法执行,一边继续执行下面的代码,仿佛开了两个线程一般?机制的科学家们想出了setTimeout方法。

  setTimeout方法想必大家都已经很熟悉了,那么setTimeout(function(){..}, a)真的是ams后执行对应的回调吗?

setTimeout(function() {
  console.log('hello world');
}, 1000);

while(true) {};

  1s中之后,控制台并没有像预料中的一样输出字符串,而网页标签上的圈圈一直转啊转,掐指一算,可能陷入while(true){}的死循环中了,可是为什么呢?虽然会陷入死循环可是也得先输出字符串啊!这就要扯到JavaScript运行机制了。

JavaScript运行机制

  一段JavaScript代码到底是如何执行的?阮一峰老师有篇不错的文章(JavaScript 运行机制详解:再谈Event Loop),我就不再重复造轮子了;如果觉得太长不看的话,楼主简短地大白话描述下。一段js代码(里面可能包含一些setTimeout、鼠标点击、ajax等事件),从上到下开始执行,遇到setTimeout、鼠标点击等事件,异步执行它们,此时并不会影响代码主体继续往下执行(当线程中没有执行任何同步代码的前提下才会执行异步代码),一旦异步事件执行完,回调函数返回,将它们按次序加到执行队列中,这时要注意了,如果主体代码没有执行完的话,是永远也不会触发callback的,这也就是上面的一段代码导致浏览器假死的原因(主体代码中的while(true){}还没执行完)。

  网上还有一篇流传甚广的文章(猛戳How JavaScript Timers Work),文章里有张很好的图,我把它盗过来了。

  文章里没有针对这幅图的代码,为了能更好的说明流程,我尝试着给出代码:

// some code

setTimeout(function() {
  console.log('hello');
}, 10);

// some code

document.getElementById('btn').click();

// some code

setInterval(function() {
  console.log('world');
}, 10);

// some code

  我们开始执行代码。第一块代码大概执行了18ms,也就是JavaScript的主体代码,在执行过程中,先触发了一个setTimeout函数,代码继续执行,只等10ms后响应setTimeout的回调,接着是一个鼠标点击事件,该事件有个回调(或许是alert一些东西),不能立即执行(单线程),因为js主体代码还没执行完,所以这个回调被插入执行队列中,等待执行;接着setInterval函数被执行,我们知道,此后每隔10ms都会有回调(尝试)插入队列中,运行到第10ms的时候,setTimeout函数的回调插入队列。js函数主体运行完后,大概是18ms这个点,我们发现队列中有个click的callback,还有个setTimeout的callback,于是我们先运行前者,在运行的过程中,setInterval的10ms响应时间也过了,同样回调被插入队列。click的回调运行完,运行setTimeout的回调,这时又10ms过去了,setInterval又产生了回调,但是这个回调被抛弃了,之后发生的事大家都一目了然了。

  这里有一点我不太明白,就是关于interval回调的drop。按照How JavaScript Timers Work里的说法是,如果等待队列里已经有同一个interval函数的回调了,将不会有相同的回调插入等待队列。

“Note that while mouse click handler is executing the first interval callback executes. As with the timer its handler is queued for later execution. However, note that when the interval is fired again (when the timer handler is executing) this time that handler execution is dropped. If you were to queue up all interval callbacks when a large block of code is executing the result would be a bunch of intervals executing with no delay between them, upon completion. Instead browsers tend to simply wait until no more interval handlers are queued (for the interval in question) before queuing more.”

  查到一篇前辈的文章Javascript定时器学习笔记,里面说“为了确保定时器代码插入到队列总的最小间隔为指定时间。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才能将定时器代码添加到代码队列中”。但是我自己实践了下觉得可能并非如此:

var startTime = +new Date;
var count = 0;
var handle = setInterval(function() {
  console.log('hello world');
  count++;
  if(count === 1000) clearInterval(handle);
}, 10);

while(+new Date - startTime < 10 * 1000) {};

  按照上文的说法,由于while对线程的“阻塞”,使得相同的setInterval的回调不能加在等待队列中,但是实际在chrome和ff的控制台都输出了1000个hello world的字符串,我也去原文博主的文章下留言询问了下,暂时还没答复我;也可能是我对setInterval的认识的姿势不对导致,如果有知道的朋友还望不吝赐教,万分感激!

  总之,定时器仅仅是在未来的某个时刻将代码添加到代码队列中,执行时机是不能保证的。

setTimeout VS setInterval

  以前看到过这样的话,setInterval的功能都能用setTimeout去实现,想想也对,无穷尽地递归调用setTimeout不就是setInterval了吗?

setInterval(function() {
  // some code
}, 10);

  根据前文描述,我们大概懂了以上setInterval回调函数的执行时间差<=10ms,因为可能会由于线程阻塞,使得一系列的回调全部在排队。用setTimeout实现的setInterval效果呢?

function func() {
  setTimeout(function() {
    // some code
    func();
  }, 10);
}

func();

setTimeout(function() {
  // some code
  setTimeout(arguments.callee, 1000);
}, 10);

  很显然两个回调之间的间隔是>10ms的,因为前面一个回调在队列中排队,如果没有等到,是不会执行下面的回调的,而>10ms是因为回调中的代码也要执行时间。换句话说,setInterval的回调是并列的,前一个回调(有没有执行)并不会影响后一个回调(插入队列),而setTimeout之间的回调是嵌套的,后一个回调是前一个回调的回调(有点绕口令的意思)

参考资料

  1. JavaScript 运行机制详解:再谈Event Loop
  2. 深入理解JavaScript定时机制
  3. How JavaScript Timers Work

2015.7.1修正

  经验证,确实是楼主对于setInterval认识的姿势有误,也对得起两个反对的差评,当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才能将定时器代码添加到代码队列中。

  楼主的示例代码,正如评论中说的一样,无论有无阻塞,都会运行1000次。代码修改如下:

var startTime = +new Date;

var handle = setInterval(function() {
  console.log('hello world');
}, 3000);

while(+new Date - startTime < 10 * 1000) {};

  如果按照之前的认识,在while阻塞过程中,setInterval应该插入了3个回调函数,而当while运行完后,控制台应该打出连续3个字符串,但是并没有,说明确实只加入了一个回调函数,其他两个被drop了。而木的树举了个更好的例子,详见Javascript定时器学习笔记评论部分的第二个示例代码。

  所以确实在使用setInterval时:

  1. 某些间隔会被跳过
  2. 多个定时器的代码执行之间的间隔可能会比预期的小(当前的setInterval回调正在执行,后一个添加)

从setTimeout谈JavaScript运行机制的更多相关文章

  1. 从setTimeout谈js运行机制

    众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执行完才能有机会执行,不像人一 ...

  2. JavaScript运行机制与setTimeout

    前段时间,老板交给了我一个任务:通过setTimeout来延后网站某些复杂资源的请求.正好借此机会,将JavaScript运行机制和setTimeout重新认真思考一遍,并将我对它们的理解整理如下. ...

  3. 深入浅出JavaScript运行机制

    一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: console.log(1); setTimeout(function(){ console.log(3); ...

  4. 深入理解JavaScript运行机制

    深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...

  5. JavaScript运行机制详解

    JavaScript运行机制详解   var test = function(){ alert("test"); } var test2 = function(){ alert(& ...

  6. javascript 运行机制 事件循环 浏览器缓存 (慕课网 前段跳槽面试必备 4-1,4-2,4-3)

    4-1 渲染机制:-1-,什么是DOCTYPE及其作用?DTD(document type definition,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型,浏览器会使 ...

  7. Javascript 运行机制

    先看一下下面这段js代码: console.log('1'); setTimeout(function(){ console.log('2'); },0); console.log('3'); 请问打 ...

  8. JavaScript 运行机制 & EventLoop

    JavaScript 运行机制 & EventLoop 看阮老师博客和自己的理解,记录的学习笔记,js的单线程和 事件EventLoop 机制. 1. JavaScript是单线程 JavaS ...

  9. javascript运行机制

    太久没更新博客了,Javascript运行机制 Record it 1.代码块 JavaScript中的代码块是指由<script>标签分割的代码段.例如: <script type ...

随机推荐

  1. 按Enter键后Form表单自动提交的问题

    怪事年年有,今年特别多. 话说,最近项目中遇到一件怪事,当我鼠标focus在文本框中,轻轻敲了下回车键,尼玛页面突然刷新了,当时把宝宝给吓得. 接下来就是一番苦逼的烧脑和蛋疼~ 一.被表象所迷惑 突然 ...

  2. Linux命令中使用正则表达式

    在使用grep.awk和sed命令时,需要使用正则表达式.比如我通过grep找代码编译结果中是否有错误.或者是否有我代码的错误.这里说下正则表达式基本的应用: • 匹配行首与行尾.• 匹配数据集.• ...

  3. linux基础-第八单元 正文处理命令及tar命令

    第八单元 正文处理命令及tar命令 使用cat命令进行文件的纵向合并 两种文件的纵向合并方法 归档文件和归档技术 归档的目的 什么是归档 tar命令的功能 tar命令的常用选项 使用tar命令创建.查 ...

  4. PHP5.2至5.6的新增功能详解

    截至目前(2014.2), PHP 的最新稳定版本是 PHP5.5, 但有差不多一半的用户仍在使用已经不在维护 [注] 的 PHP5.2, 其余的一半用户在使用 PHP5.3 [注]. 因为 PHP ...

  5. viso2010从mysql中导出ER图

    mysql connector 下载地址: http://dev.mysql.com/downloads/connector/odbc/5.1.html 首先机器要安装mysql-connector- ...

  6. node js学习(一)

    1.简介 JavaScript是一种运行在浏览器的脚本,它简单,轻巧,易于编辑,这种脚本通常用于浏览器的前端编程,但是一位开发者Ryan有一天发现这种前端式的脚本语言可以运行在服务器上的时候,一场席卷 ...

  7. 将u盘的文件复制到虚拟机上的linux系统上面—》文件挂载(文字+图解)

    虚拟机中操作系统.CentOs(无图形界面) 没有图形界面的linux,我也没有配置网络,现在需要把文件复制到linux系统上面,我这里就使用了u盘挂载的方式,获得了U盘中的文件. 1.VMware中 ...

  8. simple-spring-memcached缓存搭建

    项目中使用的缓存经常是知道使用,没有试过搭建起它.刚好这次自己的毕业可以用来搭建缓存.其他不多说了,直接看操作吧.首先在pom.xml中依赖simple-spring-memcached的架包. &l ...

  9. C++11 之 override

    1  公有继承 派生类公有继承自 (public inheritance) 基类,继承包含两部分:一是函数的 "接口" (interface),二是函数的 "实现&quo ...

  10. POJ 3608 Bridge Across Islands --凸包间距离,旋转卡壳

    题意: 给你两个凸包,求其最短距离. 解法: POJ 我真的是弄不懂了,也不说一声点就是按顺时针给出的,不用调整点顺序. 还是说数据水了,没出乱给点或给逆时针点的数据呢..我直接默认顺时针给的点居然A ...