工作中使用setTimeout解决了一个问题,于是对setTimeout的相关资料整理了下,以及对js引擎执行的原理一并整理了下,希望能给码农们一些帮助。若发现有错的地方大家及时指出,共同学习进步。

一、首先对js的单线程运行机制做一个整理;先来一张图片,直观的感受下浏览器中js是如何使用单线程机制对同步、异步函数,以及鼠标的单击事件、浏览器触发事件、Timer定时器事件(setTimeout函数)、Interval间隔执行事件(setInterval函数)的执行顺序;

对上图js主线程的运行机制进一步阐述说明:

a、所有同步任务都在主线程上执行,形成一个js的执行栈;

b、主线程之外,还存在一个"任务队列"。当异步任务有了运行结果,就往"任务队列"之中放置一个回调事件,setTimeout、setInterval等都会将回调事件塞进队列中;

c、一旦"执行栈"中的所有同步任务执行完毕,主线程就会读取"任务队列",并且按顺序依次将队列中的事件放进栈中执行,原本队列中处于等待阻塞状态的函数开始执行;

d、主线程不断重复运行c,直到队列中的函数全部运行完。

二、JavaScript引擎用单线程运行是有意义的,单线程不必理会线程同步这些复杂的问题,那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?下面结合浏览器内核处理方式简单说明。浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步。

假如某一浏览器内核的实现至少有三个常驻线程:javascript引擎线程、界面渲染线程、浏览器事件触发线程,除些以外也有一些执行完就终止的线程,如Http请求线程,这些异步线程都会产生不同的异步事件。下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的。虽然每个浏览器内核实现细节不同,但这其中的 调用原理都是大同小异。

由图可看出:浏览器中的JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可以源自 JavaScript引擎当前执行的代码块,如调用setTimeout添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变更通知等。从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来。由于单线程关系,这些任务要进行排队一个接着一个被引擎处理。

三、setTimeout执行原理说明:setTimeout设置的延迟执行时间是不能被保证的,就是说你这样写setTimeout(fn, 500)并不代表fn肯定在500毫秒之后马上就执行,延迟很可能会更长。因为 JavaScript 是单线程语言,所有的异步事件(包括计时器、鼠标事件或者一个 XMLHttpRequest 完成)仅仅当程序执行期间有缺口的时候才会执行,不是你规定了什么时候就什么时候执行,最终还是要以浏览器确定。

浏览器的更新间隔也是会影响延迟的时间,例如:IE8及其之前的IE版本更新间隔为15.6毫秒。假设你设定的setTimeout延迟为16.7ms,那么它要更新两个15.6毫秒才会该触发延时。这也意味着无故延迟了 15.6 x 2 - 16.7 = 14.5毫秒。下图以矢量表示:

16.7ms
DELAY: |------------|

CLOCK: |----------|----------|
15.6ms 15.6ms
所以即使你给setTimeout设定的延时为0ms,它也不会立即触发。目前Chrome与IE9+浏览器的更新频率都为4ms(如果你使用的是笔记本电脑,并且在使用电池而非电源的模式下,为了节省资源,浏览器会将更新频率切换至于系统时间相同,也就意味着更新频率更低)。退一步说,假使timer resolution能够达到16.7ms,它还要面临一个异步队列的问题。因为异步的关系setTimeout中的回调函数并非立即执行,而是需要加入等待队列中。但问题是,如果在等待延迟触发的过程中,有新的同步脚本需要执行,那么同步脚本不会排在timer的回调之后,而是立即执行。下图是对上述说明的直观展示:

setTimeout和setInterval之间的区别:setTimeout 和 setInterval 在执行异步代码的时候有着根本的不同,如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些);如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

举个例子:

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

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

setTimeout回调函数的执行和上一次执行之间的间隔至少有10ms(可能会更多,但不会少于10ms),而setInterval的回调函数将尝试每隔10ms执行一次,不论上次是否执行完毕。

四、举例说明setTimeout的回调函数、双重求值

1、正常使用setTimeout函数的写法如下:

setTimeout( function () { console.log(1); } , 1000);
setTimeout( function () { console.log(2); } , 800);
setTimeout( function () { console.log(3); } , 600);

很显然打印的顺序如下图所示,根据每个函数的设置的时间,第三个函数最先进入任务队列,其次第二个函数第二,最后第一个函数最后进入。所以弹出3 2 1.

2、第二种情况,不构成回调函数

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

打印的顺序如下图所示:console.log(1) ,console.log(2),console.log(3)并不是构成回调函数,所以它们根本没有进入任务队列:而是按照正常的顺序在执行栈中被执行了。

3,、第三种就是今天要说的重要问题,setTimeout的双重求值

setTimeout( “console.log(1)“ , 1000);
setTimeout( “console.log(2)“ , 800);
setTimeout( “console.log(3)“ , 600);

JavaScript像其他其他语言一样,允许在程序中提取一个包含代码的字符串,然后动态执行。其中的eval()、Function()构造函数、setTimeout()和setInterval()都可以实现双重求值;

这四个函数是将传入的字符串当做JavaScript代码执行,这个被双引号包起来的代码段就类似于函数被执行。setTimeout(" ... ", 1000)的写法等价于setTimeout(function() { ... }, 1000)的写法。因为JavaScript编程语言拥有头等函数的特性,这意味着你可以将函数直接作为参数传递给其他接口,并将他们保存在变量中或者对象的属性中;在这样的语言中, 一个函数可以作为参数传递给其他函数,可以被当作返回值被另一个函数返回,可以当作值指定给一个变量。

总而言之,setTimeout是将第一个带双引号的参数当做被传递的函数使用了,所以执行结果就如上图所示了。

Javascript引擎的单线程机制和setTimeout执行原理阐述的更多相关文章

  1. Javascript引擎单线程机制及setTimeout执行原理说明

    setTimeout用法在实际项目中还是会时常遇到.比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何 ...

  2. JavaScript引擎是单线程的

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

  3. 我想这次我真的理解了 JavaScript 的单线程机制

    今天面试的时候被问到一个问题,是关于 JS 异步的.当时我脑海中闪过了一个单线程的概念,但却没有把真正的原理阐述清楚.所以回来特意重新回顾了前面单线程和异步相关的一些知识点. 虽然之前学习的时候也接触 ...

  4. JavaScript之JS单线程|事件循环|事件队列|执行栈

    本博文基于知乎"JavaScript作用域问题?"一问,而引起了对JavaScript事件循环和单线程等概念与实践上的研究.深入理解. 一.概念 0.关键词:JavaScript单 ...

  5. JavaScript引擎理解

    JavaScript 虽然给人感觉是一个多线程执行的语言,但是其实JavaScript引擎是伪多线程,是单线程执行的, 浏览器内核:实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步.假 ...

  6. Javascript定时器(一)——单线程

    一.JavaScript 引擎是单线程的 可以从下面的代码中看到,第一个用setTimeout中的代码是死循环,由于是单线程,下面的两个定时器就没机会执行了. <script type=&quo ...

  7. Javascript线程及定时机制

    setTimeout.setInterval的使用 Javascript api文档中定义setTimeout和setInterval第二个参数意义分别为间隔多少毫秒后回调函数被执行和每隔多少毫秒回调 ...

  8. JavaScript引擎研究与C、C++与互调用(转)

    本文转自:ice6015的专栏.为什么有些招聘需要熟悉JS和C++,这或许就是原因. 1.  概要 JavaScript是一种广泛用于Web客户端开发的脚本语言,常用来控制浏览器的DOM树,给HTML ...

  9. 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?

    在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ...

随机推荐

  1. Java 单个集合去重与两个集合去重

    一.单个集合去重 描述: 去掉一个集合里重复的元素:将list集合转成hashSet集合,hashSet有自动去重的功能,再利用去重后的hashSet集合初始化一个新的list集合,此时这个list就 ...

  2. 一点css 基础

    css 行内样式优先度最高 margin 属性 为声明外边距 如图 顺序依次为上右下左

  3. Docker Ubuntu容器安装ping

    apt-get update apt-get install iputils-ping apt-get install net-tools

  4. ASP.NET实现验证码图片

    新建一个checkcode.aspx文件,页面中不用写任何东西,在代码中,Page_Load中写入如下代码: string chkCode = string.Empty;        int ix, ...

  5. JavaMaven【六、生命周期】

    Maven有三个独立的生命周期,每个生命周期都不会出发别的生命周期的操作 若直接执行生命周期后面的操作,maven会默认执行前面的操作 如项目创建好后,直接执行mvn install,会默认依次执行c ...

  6. MySQL数据库笔记四:MySQL的约束

    <1>概念 是一种限制,它是对表的行和列的数据做出约束,确保表中的数据的完整性和唯一性. <2>使用场景 创建表的时候,添加约束 <3>分类 1. default: ...

  7. oracle字符集问题随笔

    oracle字符集问题: 1.select * from nls_database_parameters where parameter in ('NLS_LANGUAGE','NLS_TERRITO ...

  8. PAT Basic 1051 复数乘法 (15 分)

    复数可以写成 ( 的常规形式,其中 A 是实部,B 是虚部,i 是虚数单位,满足 1:也可以写成极坐标下的指数形式 (,其中 R 是复数模,P 是辐角,i 是虚数单位,其等价于三角形式 (. 现给定两 ...

  9. python操作kafka实践

    1.先看最简单的场景,生产者生产消息,消费者接收消息,下面是生产者的简单代码. ------------------------------------------------------------ ...

  10. BPTT

    RNN 的 BP —— Back Propagation Through Time. 参考:零基础入门深度学习(5) - 循环神经网络.知乎. 1 def backward(self, sensiti ...