其实说起JavaScript中的定时器(Timer)中的 setTimeout() 方法,从事开发的同学想必都不会陌生,觉得这些东西很简单很基础。但是有时候恰恰是基础简单的东西,才越容易被忽略。先看一段代码:

console.log("start");setTimeout(function(){console.log("world")
},200);setTimeout(function(){console.log("Hello ")
},100);console.log("end");

上面这段代码大家都知道会输出什么,结果如下:

下面我们稍微改动一下代码,在中间添加一个循环,如下:

console.log("start");setTimeout(function(){console.log("world")
},200);for(let i = 0; i<1000; i++){console.log("我就是来耗时间的!")
}setTimeout(function(){console.log("Hello ")
},100);

这次的结果有点意外,延迟200毫秒的”world”先输出来了,结果如下:

这就有点奇怪了,js代码是怎么执行的呢?

JS代码是怎么执行的

那首先应该从JS代码执行说起,JS引擎会在内存中分配堆区(heap)和栈区(stack),那么JS又是以怎样的顺序执行的呢?先看一段很简单的代码:

function A(){var a = 3;B(3);
}
function B(num){var newNum = num * num;console.log(newNum);
}

当代码运行的时候,为便于理解我们假设栈底有一个main()方法(类似于Java中的入口Main方法)作为运行的开始。

1.代码开始执行:

2.运行A()方法,将A()入栈,此时A上下文中存在变量 a = 3

3.调用B()方法,将B()入栈,此时B上下文中存在变量 num = 3,newNum = 9

4.调用B()方法中的console.log(),入栈,控制台打印 9

5.继续运行,依次将console.log(),和B()方法出栈

6.A()方法运行完毕出栈

7.代码运行完成,清空栈

这就是一段简单的代码在堆栈中的执行情况,看明白了就能开始介绍Javascript引擎的另外一个机制。

Event Loop

众所周知,Javascript引擎(以下简称JS引擎)是单线程的,在某一个特定的时间内只能执行一个任务,并阻塞其他任务的执行,也就是说这些任务是串行的。这样的话,用户不得不等待一个耗时的操作完成之后才能进行后面的操作,这显然是不能容忍的,但是实际开发中我们却可以使用异步代码来解决。

举个特殊栗子——计算机CPU,我们可以听着音乐的同时愉快的码代码,看起来播放音乐和编辑代码是并行的,其实不然。在计算机中并没有绝对意义上的并行,从微观上来看,单核心的CPU其实在同一个时间片内只能处理单一的任务,一旦某个进程的时间片结束,CPU会马上调度另一个进程执行,先前的进程则处于挂起状态等待获得时间片后继续执行,如此反复,宏观上看起来这些任务就是并行处理的。

回到我们熟悉的JS引擎,为实现这样的特性,这里就需要引申出一个重要的东西,Event Loop(事件循环)。

当异步方法比如这里的setTimeout(),或者ajax请求、DOM事件执行的时候,会交由浏览器内核的其他模块去管理。当异步的方法满足触发条件后,该模块就会将方法推入到一个任务队列(task queue)中,当主线程代码执行完毕处于空闲状态的时候,就会去检查任务队列,将队列中第一个任务入栈执行,完毕后继续检查任务队列,如此循环。前提条件是主线程处于空闲状态,这就是事件循环的模型。

SetTimeout

明白了上面的东西,那理解setTimeout的机制就容易得多了,看下面一段代码:

console.log("start");setTimeout(function(){console.log("hello")
},200);setTimeout(function(){console.log("world")
},100);console.log("end");

为了让大家更直观的看到执行的顺序,做了一个GIF

图上可以看出,首先依然是main()开始,首先第一个console.log()入栈执行,执行完毕控制台打印’start’后出栈,紧接着执行到setTimeout定时器,此时JS引擎会将定时器交给浏览器的另一个模块去管理(为方便理解这里把它叫做Timer模块),然后主线程继续向下执行,紧接着将第二个定时器也交给Timer模块,然后执行到第二个console.log(),控制台打印’end’,执行完毕后清空执行栈。但是并没有结束,在主线程执行的同时,Timer模块会检查其中的异步代码,一旦满足触发条件,就会将它添加到任务队列中。Timer2延迟100ms,所以会早于Timer1被添加到队列排头。而主线程此时处于空闲状态,所以会检查任务队列是否有待执行的任务。此时会将Timer2回调中的console.log()执行,控制台打印’world’,然后执行栈空闲后继续检查任务队列,将Timer1的代码压入执行栈中执行,控制台打印’hello’,清空执行栈,此时任务队列为空,执行结束。

控制台依次打印出:

这个时候我们在看下面这个程序,就很好懂了:

console.log("start");//Timer1
setTimeout(function(){console.log("hello");
},200);//Timer2
setTimeout(function(){console.log("world");
},300);//耗时运算
for(let i = 0; i<10000; i++){console.log("我就是来耗时间的");
}//Timer3
setTimeout(function(){console.log("I am run");
},100);console.log("end");

和第一段不同的是,在最后一个定时器前加了一段for循环,(注:此处仅用来模拟一段比较耗时的运算,假设时间大于1秒,代码真正执行时间不必深究)。chrome控制台运行结果是:

前面的那张Gif图看懂了以后,其实从’world’之前的打印应该都是没有问题的。

但是奇怪的地方就是,Timer3仅仅延迟了100ms,反而在另外两个Timer之后执行了。其实这里原因很简单,因为在Timer1和Timer2加入到执行队列中后,主线程依然还在执行for循环中的代码,处于阻塞状态。队列中的Timer1和Timer2并不会得以执行。当for循环结束,这时才将Timer3交由Timer模块去管理,继续执行后续代码打印’end’,清空执行栈。虽然在这里Timer3的延迟时间最短,但是加入任务队列后还是会排在Timer1和Timer2的后面,所以此时按顺序执行任务队列中的代码,依次打印’hello’、’world’、’I am run’。同时需要注意的是,这种情况下的三个定时器延迟执行的时间已经远远超过了指定的时间。

还有一点值得特别注意的是,有些同学可能会写过这样的代码:

setTimeout(function(){},0);

其实JS引擎在处理这段代码的时候,并不是真正的延迟0ms执行。不同的浏览器会默认有一个最小的延迟时间,低于这个时间间隔会按照默认最小的时间间隔来处理。

总结

我们发现不论事件循环(Event Loop)模型还是setTimeout机制,其实并不是难点,但却是很多开发同学容易忽略的点。很多问题的产生可能就是因为忽略了一些简单的原理导致的。所以这些基本的知识点需要掌握扎实,才能更好的驾驭Javascript。

探索setTimeout相关推荐

  1. 一个拖拽卡顿问题引发出对setTimeOut的探索

    海阔凭鱼跃,天高任鸟飞.Hey 你好!我是秦爱德.

  2. 由于开发者通过接口修改了菜单配置_Android SDK开发艺术探索(四)个性化配置...

    一.前言 本篇是Android SDK开发艺术探索系列的第四篇文章.介绍了通过流式API设计思想优雅地实现SDK的自定义选项配置需求. 目录概览: 一.前言 二.SDK自定义配置2.1.什么是自定义配 ...

  3. [探索] 利用promise做一个请求锁

    在最近开发小程序的过程中,遇到一个需求,就是请求的时候header需要带上accessToken, accessToken是通过登陆接口返回的参数,可能会出现过期的情况,则需要重新登陆,所以每次加载小 ...

  4. setTimeout,setInterval你不知道的事

    javascript线程解释(setTimeout,setInterval你不知道的事) 标签: javascript引擎任务浏览器functionxmlhttprequest 2011-11-21 ...

  5. 知识图谱可视化技术在美团的实践与探索

    省时查报告-专业.及时.全面的行研报告库 省时查方案-专业.及时.全面的营销策划方案库 [免费下载]2022年3月份热门报告盘点 某短视频APP推荐算法及策略最详细拆解 大萧条来临前的几大征兆 机器学 ...

  6. pythonrsv分割_JavaScript是如何工作: 深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!...

    文章底部分享给大家一套 react + socket 实战教程 这是专门探索 JavaScript 及其所构建的组件的系列文章的第5篇. 想优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! ...

  7. 前端技术探索 - 你不知道的JS 沙箱隔离

    点击上方关注 前端技术江湖,我们一起学习,天天进步 一些「炒冷饭」背景介绍 本文并不会从头开始介绍 Web Worker 的基础知识和基本 API 的使用等(只是部分有涉及),若还未了解过 Web W ...

  8. setTimeout原理

    setTimeout原理 setTimeout的原理: function a() { setTimeout(function(){alert(1)},0); alert(2); } a(); 和其他的 ...

  9. Taro跨端开发探索19——商城小程序确认订单页面开发

    前言 截止到昨天,我们已经将商城小程序的所有tabBar下对应的所有页面.到现在可以说我们的小程序的业务逻辑已经完成了70%了,剩下的逻辑比较重点的就是订单和售后页面了. 今天我们开始探索确认订单页面 ...

最新文章

  1. 普通人CV领域论文创新常见思路与方向总结
  2. 微软职位内部推荐-Senior Software Engineer
  3. Wireshark如何选择多行
  4. svn 1.8.11 命令行提交新添加文件错误
  5. Will Wright总是能给我们带来惊奇啊
  6. Sales area data model research in QDD
  7. 牛客网【每日一题】5月22日 [CQOI2009]中位数图
  8. jquery 获取键值对中最大值_jQuery的AJAX发送数据键值对
  9. 存储过程生成所有实体类
  10. visual studio 最新稳定版本_速来围观!Android Studio 4.0 稳定版发布了
  11. 大数据对能源发展有什么作用
  12. LabView学习笔记——简易入门AND初级实战项目(计算器)
  13. srs信道估计_信道估计(channel estimation)图解——从SISO到MIMO原理介绍
  14. 基于数字孪生的IBV智能建筑可视化系统了解一下
  15. 2022年湖南省中医执业医师考试第三单元医学针灸学模拟题
  16. 今天开通个人博客,值得祝贺!
  17. EPA PMF正定因子分解模型程序破解
  18. github push 出错:fatal: Authentication failed for 'https://github.com/ ..的解决
  19. GooglePlay应用上架完整流程
  20. clipper运行以及使用——轻松一刻

热门文章

  1. 支付app 支付 弹出 ALIN42273
  2. 【笔记】Cocos2d-x高级开发教程:制作自己的捕鱼达人 笔记一:序_前言_第一章
  3. Allegro artwork pastemask顶层和pastemask底层需要出那些信息
  4. 当出现项目出现,java: 找不到符号,符号:XXX,位置:XXX
  5. STM32F030 HAL库硬件SPI操作W25Q16存储芯片(二)
  6. Kitchen Plates(暴力写法和学习拓扑排序)
  7. 腾讯为Facebook发布QQ Chat
  8. java 根据html模板生成html文件
  9. 解决Hash(哈希表)冲突的四种方案
  10. 倒计时 3 天 | 海豚调度对话 Apache ShenYu(Incubating)核心开发,揭秘玩转DataOps “绝杀技”...