JavaScrip单线程引擎工作原理分析

从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的。定时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的。我们先来认识一下下面三个函数是如何控制计时器的。

我们推荐阅读:雕虫无小技 JavaScript初学者的10个迷你技巧

代码如下:

var id = setTimeout(fn, delay);

初始化一个计时器,然后在指定的时间间隔后执行。该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器。


代码如下:

var id = setInterval(fn, delay);

和setTimeout有些类似,但它是连续调用一个函数(时间间隔是delay参数)直到它被取消。


代码如下:

clearInterval(id);, clearTimeout(id);

使用计时器ID(setTimeout 和 setInterval的返回值)来取消计时器回调的发生。

为了理解计时器的内在执行原理,有一个重要的概念需要加以探讨:计时器的延迟(delay)是无法得到保障的。由于所有JavaScript代码是在一个线程里执行的,所有异步事件(例如,鼠标点击和计时器)只有拥有执行机会时才会执行。用一个很好的图表加以说明:

在这个图表中有许多信息需要理解,如果完全理解了它们,你会对JavaScript引擎如何实现异步事件有一个很好的认识。这是一个一维的图标:垂直方向表示时间,蓝色的区块表示JavaScript代码执行块。例如第一个JavaScript代码执行块需要大约18ms,鼠标点击所触发的代码执行块需要11ms等等。

由于JavaScript引擎同一时间只执行一条代码(这是由于JavaScript单线程的性质),所以每一个JavaScript代码执行块会“阻塞”其它异步事件的执行。这就意味着当一个异步事件发生(例如,鼠标点击,计时器被触发,或者Ajax异步请求)后,这些事件的回调函数将排在执行队列的最后等待执行(实际上,排队的方式根据浏览器的不同而不同,所以这里只是一个简化);

从第一个JavaScript执行块开始研究,在第一个执行块中两个计时器被初始化:一个10ms的setTimeout()和一个10ms的setInterval()。依据何时何地计时器被初始化(计时器初始化完毕后就会开始计时),计时器实际上会在第一个代码块执行完毕前被触发。但是,计时器上绑定的函数不会立即执行(不被立即执行的原因是JavaScript是单线程的)。实际上,被延迟的函数将依次排在执行队列的最后,等待下一次恰当的时间再执行。

此外,在第一个JavaScript执行块中我们看到了一个“鼠标点击”事件发生了。一个JavaScript回调函数绑定在这个异步事件上了(我们从来不知道用户什么时候执行这个(点击)事件,因此认为它是异步的),这个函数不会被立即执行,和上面的计时器一样,它将排在执行队列的最后,等待下一次恰当的时候执行。

当第一个JavaScript执行块执行完毕后,浏览器会立即问一个问题:哪个函数(语句)在等待被执行?在这时,一个“鼠标点击事件处理函数”和一个“计时器回调函数”都在等待执行。浏览器会选择一个(实际上选择了“鼠标点击事件的处理函数”,因为由图可知它是先进队的)立即执行。而“计时器回调函数”将等待下次适合的时间执行。

注意,当“鼠标点击事件处理函数”执行的时候,setInterval的回调函数第一次被触发了。和setTimeout的回调函数一样,它将排到执行队列的最后等待执行。但是,一定要注意这一点:当setInterval回调函数第二次被触发时(此时setTimeout函数仍在执行)setTimeout的第一次触发将被抛弃掉。当一个很长的代码块在执行时,可能把所有的setInterval回调函数都排在执行队列的后面,代码块执行完之后,结果便会是一大串的setInterval回调函数等待执行,并且这些函数之间没有间隔,直到全部完成。所以,浏览器倾向于的当没有更多interval的处理函数在排队时再将下一个处理函数排到队尾(这是由于间隔的问题)。

我们能够发现,当第三个setInterval回调函数被触发时,之前的setInterval回调函数仍在执行。这就说明了一个很重要的事实:setInterval不会考虑当前正在执行什么,而把所有的堵塞的函数排到队列尾部。这意味着两次setInterval回调函数之间的时间间隔会被牺牲掉(缩减)。

最后,当第二个setInterval回调函数执行完毕后,我们可以看到没有任何程序等待JavaScript引擎执行了。这就意味着浏览器现在在等待一个新的异步事件的发生。在50ms时一个新的setInterval回调函数再次被触发,这时,没有任何的执行块阻塞它的执行了。所以它会立刻被执行。让我们用一个例子来阐明setTimeout和setInterval之间的区别:

代码如下:

setTimeout(function(){
/* Some long block of code... */
setTimeout(arguments.callee, 10);
 }, 10);
 
 setInterval(function(){
/* Some long block of code... */
 }, 10);

setTimeout(function(){
/* Some long block of code... */
setTimeout(arguments.callee, 10);
 }, 10);
 
 setInterval(function(){
/* Some long block of code... */
 }, 10);

这两句代码乍一看没什么差别,但是它们是不同的。setTimeout回调函数的执行和上一次执行之间的间隔至少有10ms(可能会更多,但不会少于10ms),而setInterval的回调函数将尝试每隔10ms执行一次,不论上次是否执行完毕。

总结

◆JavaScript引擎是单线程的,强制所有的异步事件排队等待执行;

◆setTimeout和 setInterval在执行异步代码的时候有着根本的不同;

◆如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些);

◆如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

上述这些知识点都是非常重要的。了解了JavaScript引擎是如何工作的,尤其是大量的异步事件(连续)发生时,才能为构建高级应用程序打好基础。

原文作者:John Resig

原文链接: http://ejohn.org/blog/how-javascript-timers-work/

(0)

相关推荐

  • JavaScrip单线程引擎工作原理分析

    从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的.定时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的.我们先来认识一下下面三个函数是如何控制计时器的. 我们推荐阅读:雕虫无小技 JavaScript初学者的10个迷你技巧 复制代码 代码如下: var id = setTimeout(fn, delay); 初始化一个计时器,然后在指定的时间间隔后执行.该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器. 复制代码

  • javascript 定时器工作原理分析

    setTimeout() MDN对 setTimeout 的定义为: 在指定的延迟时间之后调用一个函数或执行一个代码片段. 语法 setTimeout 的语法非常简单,第一个参数为回调函数,第二个参数为延时的时间.函数返回一个数值类型的ID唯一标示符,此ID可以用作 clearTimeout 的参数来取消定时器: var timeoutID = window.setTimeout(code, delay); IE0+ 还支持回调参数的传入: var timeoutID = window.setT

  • 分析Tomcat的工作原理

    SpringBoot 就像一条巨蟒,慢慢缠绕着我们,使我们麻痹.不得不承认,使用了 SpringBoot 确实提高了工作效率,但同时也让我们遗忘了很多技能.刚入社会的时候,我还是通过 Tomcat 手动部署 JavaWeb 项目,还经常对 Tomcat 进行性能调优.除此之外,还需要自己理清楚各 Jar 之间的关系,以避免 Jar 丢失和各版本冲突导致服务启动异常的问题.到如今,这些繁琐而又重复的工作已经统统交给 SpringBoot 处理,我们可以把更多的精力放在业务逻辑上.但是,清楚 Tom

  • 模板引擎smarty工作原理以及使用示例

    模板引擎是用于把模板文件和数据内容合并在一起的程序,便于网站开发有利于代码分离和维护,了解一个模板最好知道其工作原理,以便于实现一通万通. 模板文件一般是HTML xml js等类型文件,如果不用模板引擎若要把数据显示在网页上,我们需要在php中输出HTML,而使用模板则只要把数据交给模板引擎程序即可,然后告诉它用哪个模板文件,自然就会把数据和页面结合以后返回或输出,模板至少有以下功能1.把数据提供给模板引擎的功能.2.指定模板的功能.3.输出结果的功能.一般来说为了方便程序员们使用模板引擎,开

  • Yii中srbac权限扩展模块工作原理与用法分析

    本文实例讲述了Yii中srbac权限扩展模块工作原理与用法.分享给大家供大家参考,具体如下: 1. 设置权限规则表:可放在module模块配置文件里面 public function init() { //操作权限表,必须存在以下字段: //itemname角色名/ID, //type授权项目类型/1(任务)或者2(角色), //bizrule权限/逻辑运算表达式为false是有权限操作, //data数据/YII暂无利用 Yii::app()->authManager->itemTable

  • 代码分析Spring MVC的工作原理

    遗留问题 在关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题中,我遗留了一个问题:Spring mvc是何时.何地.如何将Model中的属性绑定到哪个作用域,这里的作用域指的是Servlet的四大作用域:不了解问题背景的可以回过头去看看我的上篇博文. 明确的解答我会放到最后,在解答问题之前,我先和大家一起来捋一捋Spring mvc的工作原理.废话不多说,开始我们神秘的探险之旅! 应用示例 在讲工作原理之前,我们先看一个简单的spring mvc(ssm)示例,以及实现的效果

  • Java虚拟机工作原理

    首先我想从宏观上介绍一下Java虚拟机的工作原理.从最初的我们编写的Java源文件(.java文件)是如何一步步执行的,如下图所示,首先Java源文件经过前端编译器(javac或ECJ)将.java文件编译为Java字节码文件,然后JRE加载Java字节码文件,载入系统分配给JVM的内存区,然后执行引擎解释或编译类文件,再由即时编译器将字节码转化为机器码.主要介绍下图中的类加载器和运行时数据区两个部分. 类加载 类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区

  • Python构建网页爬虫原理分析

    既然本篇文章说到的是Python构建网页爬虫原理分析,那么小编先给大家看一下Python中关于爬虫的精选文章: python实现简单爬虫功能的示例 python爬虫实战之最简单的网页爬虫教程 网络爬虫是当今最常用的系统之一.最流行的例子是 Google 使用爬虫从所有网站收集信息.除了搜索引擎之外,新闻网站还需要爬虫来聚合数据源.看来,只要你想聚合大量的信息,你可以考虑使用爬虫. 建立一个网络爬虫有很多因素,特别是当你想扩展系统时.这就是为什么这已经成为最流行的系统设计面试问题之一.在这篇文章中

  • Vue.js原理分析之nextTick实现详解

    前言 tips:第一次发技术文章,篇幅比较简短,主要采取文字和关键代码表现的形式,希望帮助到大家.(若有不正确还请多多指正) nextTick作用和用法 用法:nextTick接收一个回调函数作为参数,它的作用是将回调延迟到下一次DOM更新之后执行,如果没有提供回调函数参数且在支持Promise的环境中,nextTick将返回一个Promise. 适用场景:开发过程中,开发者需要在更新完数据之后,需要对新DOM做一些操作,其实我们当时无法对新DOM进行操作,因为这时候还没有重新渲染,这时候nex

  • 详解React Fiber的工作原理

    啥是React Fiber? React Fiber,简单来说就是一个从React v16开始引入的新协调引擎,用来实现Virtual DOM的增量渲染. 说人话:就是一种能让React视图更新过程变得更加流畅顺滑的处理手法. 我们都知道:进程大,线程小.而Fiber(纤维)是一种比线程还要细粒度的处理机制.从这个单词也可以猜测:React Fiber会很"细".到底怎么个细法,我们接着往下看. 为什么会有React Fiber? 之前说了,React Fiber是为了让React的视

随机推荐