平时的工作中,也许你会经常用到setTimeout这个方法,可是你真的了解setTimeout吗?本文想通过总结setTimeout的用法,顺便来探索javascript里面的事件执行机制。

setTimeout基本用法

1、

  1. setTimeout(code,millisec)

setTimeout函数接受两个参数,第一个参数code是将要推迟执行的函数名或者一段代码,第二个参数millisec是推迟执行的毫秒数。

例如:

  1. setTimeout(‘console.log(2)’,100);
  2.  
  3. setTimeout(function(){console.log(2)},100);

如果直接在setTimeout中直接执行代码, 需要以字符串的形式去写,引擎内部会将字符串转为可执行的代码

2、再来一些简单些的代码

  1. console.log(1);
  2. setTimeout('console.log(2)',1000);
  3. console.log(3);

是的,如你所愿,依次输出的是  // 1 3 2

3、代码升级版

  1. console.log(1);
  2.  
  3. setTimeout(function(){
  4. console.log(2);
  5. },300);
  6.  
  7. setTimeout(function(){
  8. console.log(3)
  9. },400);
  10.  
  11. for (var i = 0;i<10000;i++) {
  12. console.log(4);
  13. }
  14. setTimeout(function(){
  15. console.log(5);
  16. },100);

这个时候的输入顺序是怎样的呢?这里先埋个伏笔,因为我们是以setTimeout来聊Event Loop

Event Loop简介

什么是Event Loop呢?

因为javascript是单线程的,所谓的单线程是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个,可以叫它为主线程

除了主线程,还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。我们以setTimeout为例,当在代码中调用setTimeout()方法时,注册的延时方法会交由浏览器内核其他模块(以webkit为例,是webcore模块)处理,当延时方法到达触发条件,即到达设置的延时时间时,这一延时方法被添加至任务队列里。这一过程由浏览器内核其他模块处理,与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中顺序获取任务来执行,这一过程是一个不断循环的过程,称为事件循环模型。

  Javascript执行引擎的主线程运行的时候,产生堆(heap)和栈(stack)。程序中代码依次进入栈中等待执行,当调用setTimeout()方法时,即图中右侧WebAPIs方法时,浏览器内核相应模块开始延时方法的处理,当延时方法到达触发条件时,方法被添加到用于回调的任务队列,只有执行引擎栈中的代码执行完毕,主线程才会去读取任务队列,依次执行那些满足触发条件的回调函数。

  在上图中的callback queue中指的是 "任务队列",也可以理解为消息的队列,“消息“我们可以简单理解为是:注册异步任务时添加的回调函数。

例如:

  1. setTimeout(function(){
  2. console.log(‘hello’);
  3. },100);

其中里面的function(){console.log('hello')}就是一个消息,任务队列里面保存的就是这些回调函数

理解js代码的执行

我们以一段代码的运行来进行理解,代码如下:

  1. console.log('start');
  2.  
  3. //Timer1
  4. setTimeout(function(){
  5. console.log('hello');
  6. },200);
  7.  
  8. //Timer2
  9. setTimeout(function(){
  10. console.log('world');
  11. },100);
  12.  
  13. console.log('end');

代码运行的gif图如下:

我们分步骤来进行这个过程解答

   1、 js执行引擎开始执行上述代码时,会先讲一个main()方法加入执行栈。首先第一个console.log(‘start’)入栈,console.log方法是一个webkit内核支持的普通方法,而不是前面图中WebAPIs涉及的方法,所以这里log('start')方法立即出栈被引擎执行。

2、引擎继续往下,将setTimeout(callback,200)添加到执行栈。setTimeout()方法属于事件循环模型中WebAPIs中的方法,引擎在将setTimeout()方法出栈执行时,将延时执行的函数交给了相应模块,即图右方的timer模块来处理。

   3、然后主线程继续向下执行,紧接着将第二个定时器也交给Timer模块,然后执行到第二个console.log(),控制台打印'end',

4、执行完毕后清空执行栈。但是并没有结束,在主线程执行的同时,Timer模块会检查其中的异步代码,一旦满足触发条件,就会将它添加到任务队列中。Timer2延迟100ms,所以会早于Timer1被添加到队列排头。而主线程此时处于空闲状态,所以会检查任务队列是否有待执行的任务。此时会将Timer2回调中的console.log()执行,控制台打印'world',然后执行栈空闲后继续检查任务队列,将Timer1的代码压入执行栈中执行,控制台打印'hello',清空执行栈,此时任务队列为空,执行结束,程序处理完毕,main()方法也出栈。

  5、在这里再次强调一下,不是setTimeout加入了事件队列,而是setTimeout里面的回调函数加入了事件队列

setTimeout问题的解答

回到我们文章之初的那倒题:

  1. console.log(1);
  2. //Time1
  3. setTimeout(function(){
  4. console.log(2);
  5. },300);
  6. //Time2
  7. setTimeout(function(){
  8. console.log(3)
  9. },400);
  10.  
  11. for (var i = 0;i<10000;i++) {
  12. console.log(4);
  13. }
    //Time3
  14. setTimeout(function(){
  15. console.log(5);
  16. },100);

如果理解了上面的内容,那么这道题理解起来就比较容易了。

首先是打印出 1,然后是 10000个4,那么Time1、Time2、Time3是顺序是如何的呢?

  在这个代码中,for循环比较耗时,在Time1和Timer加入到执行队列中后,主线程依然还在执行for循环中的代码,处于阻塞状态。队列中的Time1和Time2并不会得以执行。当for循环结束,这时才将Time3交由Timer模块去管理,清空执行栈。虽然在这里Time3的延迟时间最短,但是加入任务队列后还是会排在Time1和Time2的后面,所以此时按顺序执行任务队列中的代码,依次打印2、3、5。

所以执行结果为:

关于setTimeout新的问题

  1. console.log(1);
  2. //Time1
  3. setTimeout(function(){
  4. console.log(2);
  5. },300);
  6. //Time2
  7. setTimeout(function(){
  8. console.log(3)
  9. },400);
  10.  
  11. for (var i = 0;i<10000;i++) {
  12. console.log(4);
  13. }
  14. //Time3
  15. setTimeout(function(){
  16. console.log(5);
  17. },100);

上面这个问题中,Time3加入任务队列的时间比Time2,Time1晚,所以它是最后才执行的。那么问题来了,请看下面代码:

  1. console.log(1);
  2.  
  3. //Time2
  4. setTimeout(function(){
  5. console.log(3)
  6. },400);
  7.  
  8. //Time1
  9. setTimeout(function(){
  10. console.log(2);
  11. },300);
  12.  
  13. for (var i = 0;i<10000;i++) {
  14. console.log(4);
  15. }
  16. //Time3
  17. setTimeout(function(){
  18. console.log(5);
  19. },100);

我们将Time1和Time2的顺序对换一下,按照前面的说法,Time2先加入任务队列,然后是Time1,再然后是Time3。可是执行的结果还是1、4、2、3、5,这是为什么呢?虽然Time1的执行时间短,可是它比Time2晚加入任务队列啊。

为了验证这个问题,我们可以提出这样的一个假设:

    如果setTimeout加入队列的阻塞时间大于两个setTimeout执行的间隔时间,那么先加入任务队列的先执行,尽管它里面设置的时间比另一个setTimeout的要大

可能假设听起来比较拗口,我们可以用代码来理解一下:

代码1:

  1. //Time2
  2. setTimeout(function(){
  3. console.log(2);
  4. },400);
  5.  
  6. var start=new Date();
  7. for (var i = 0;i<5000;i++) {
  8. console.log('这里只是模拟一个耗时操作');
  9. };
  10. var end=new Date();
  11. console.log('阻塞耗时:'+Number(end-start)+'毫秒');
  12.  
  13. //Time1
  14. setTimeout(function(){
  15. console.log(3)
  16. },300);

Time1比Time2设定的执行时间早100ms,但是Time2先加入任务队列,在Time2和Time1时间有一个阻塞的for循环,执行结果如下:

Time2先执行;

代码2:

我们把for循环里面的时间设置短一点:

  1. setTimeout(function(){
  2. console.log(2);
  3. },400);
  4.  
  5. var start=new Date();
  6. for (var i = 0;i<500;i++) {
  7. console.log('这里只是模拟一个耗时操作');
  8. };
  9. var end=new Date();
  10. console.log('阻塞耗时:'+Number(end-start)+'毫秒');
  11.  
  12. //Time1
  13. setTimeout(function(){
  14. console.log(3)
  15. },300);

此时,Time1先执行,因为阻塞的耗时小于Time1和Time2的执行间隔时间100毫秒;

代码3:

我们再来验证一下,把Time2的执行时间设为350毫秒;

  1. //Time2
  2. setTimeout(function(){
  3. console.log(2);
  4. },350);
  5.  
  6. var start=new Date();
  7. for (var i = 0;i<500;i++) {
  8. console.log('这里只是模拟一个耗时操作');
  9. };
  10. var end=new Date();
  11. console.log('阻塞耗时:'+Number(end-start)+'毫秒');
  12.  
  13. //Time1
  14. setTimeout(function(){
  15. console.log(3)
  16. },300);

直接结果为:

Time2先执行,因为阻塞的时间大于两个setTimeout之间的间隔时间。

通过上面的假设,我们可以得出这样一个结论:如果setTimeout加入队列的阻塞时间大于两个setTimeout执行的间隔时间,那么先加入任务队列的先执行,尽管它里面设置的时间可能比另一个setTimeout的要大

总结

  理解js的事件循环在平时的工作中还是挺有用的,它可以让我们清楚的知道事件的执行顺序,知道事件的走向,才能更好的驾驭Javascript。本文是对事件循环的一个小小总结,更多的干货,可以看看下面的参考文档。本文有误之处,欢迎指出

参看文档:

【转向Javascript系列】从setTimeout说事件循环模型

JavaScript 运行机制详解:再谈Event Loop

干货 | 原来你是这样的 setTimeout

JavaScript:彻底理解同步、异步和事件循环(Event Loop)

以setTimeout来聊聊Event Loop的更多相关文章

  1. 【原】以setTimeout来聊聊Event Loop

    平时的工作中,也许你会经常用到setTimeout这个方法,可是你真的了解setTimeout吗?本文想通过总结setTimeout的用法,顺便来探索javascript里面的事件执行机制. setT ...

  2. 定时器setTimeout()和Node.js的Event Loop

    一.定时器 setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行.它在"任务队列"的尾部添加一个事件,因此要等到同步任务和 ...

  3. setTimeout 的黑魔法 【event loop】

    setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字--定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次 ...

  4. setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop

    笔者以前面试的时候经常遇到写一堆setTimeout,setImmediate来问哪个先执行.本文主要就是来讲这个问题的,但是不是简单的讲讲哪个先,哪个后.笼统的知道setImmediate比setT ...

  5. javascript运行模式:并发模型 与Event Loop

    看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...

  6. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

  7. 数据密集型 和 cpu密集型 event loop

    Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速.可伸缩的网络程序.Node.js采用的事件驱动.非阻塞I/O模型使它既轻量又高效,是构建 ...

  8. 【Node.js】Event Loop执行顺序详解

    本文基于node 0.10.22版本 关于EventLoop是什么,请看阮老师写的什么是EventLoop 本文讲述的是EventLoop中的执行顺序(着重讲setImmediate, setTime ...

  9. [Javascript] Task queue & Event loop.

    Javascript with Chorme v8 engine works like this : For Chorme engine, v8, it has call stack. And all ...

随机推荐

  1. UVa 374 - Big Mod

    题目大意:计算R = BP mod M,根据模运算的性质计算. 正常计算会超时,可以用分治的思想降低时间复杂度.不过如果遇到00,结果...话说00的结果是1吗?忘了都... #include < ...

  2. Centos7.2 编译安装PHP7

    PHP7,编译安装: 环境:centos7.2    (注意:因为我用的nginx, 此配置参数没有考虑到apache,所以不合适需要用apache的朋友照搬过去运行,但是可以参考.)   直接下载P ...

  3. BZOJ3202 [Sdoi2013]项链

    Problem E: [Sdoi2013]项链 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 427  Solved: 146[Submit][Sta ...

  4. HNU 13073 Ternarian Weights 解题报告

    本题大意: 用天平对一物品进行称重,现有重量不同的砝码,砝码的重量分别为:1,3,9,27,..,3^n.(n<20) 天平的右侧放砝码,左侧放物品或物品和砝码,使得左右两边的重量相等. 现有一 ...

  5. 关于android:id="@+id/xx"的理解

    之前学习android的时候,对android:id="@+id/xx"总感觉混淆,不理解,刚看了一篇文章.现在貌似是恍然大悟.故做了一下笔记,希望帮助有共同问题的人... and ...

  6. 关于js中for in的缺陷浅析

    关于js中for in的缺陷浅析 http://www.jb51.net/article/44028.htm

  7. 列表视图(ListView)和ListActivity

    ListView是手机系统中使用非常广泛的一种组件,它以垂直列表的形式显示所有列表项. 创建ListView有如下两种方式: 直接使用ListView进行创建. 让Activity继承ListActi ...

  8. 数字操作 转为false的类型 typeof操作符 isNaN函数

    console.group('数字操作'); // 浮点数值的内存空间是整数的两倍: // 会alert出来3e-7;从小数点后面6个0开始,就用科学计数法了: //alert(0.0000003); ...

  9. java操作redis redis连接池

    redis作为缓存型数据库,越来越受到大家的欢迎,这里简单介绍一下java如何操作redis. 1.java连接redis java通过需要jedis的jar包获取Jedis连接. jedis-2.8 ...

  10. python中关于字符串的操作

    Python 字符串操作方法大全 python字符串操作实方法大合集,包括了几乎所有常用的python字符串操作,如字符串的替换.删除.截取.复制.连接.比较.查找.分割等,需要的朋友可以参考下 1. ...