Javascript引擎的单线程机制和setTimeout执行原理阐述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

工作中使用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是将第一个带双引号的参数当做被传递的函数使用了,所以执行结果就如上图所示了。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: JavaScriptJava

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

PyTorch与PyTorch Geometric的安装过程是什么 - 开发技术

这篇文章主要讲解了“PyTorch与PyTorch Geometric的安装过程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PyTorch与PyTorch Geometric的安装过程是什么...

数字函数

coerce:返回一个包含类型转换完毕的两个数值元素的元组。 >>> coerce(1.3,134L) (1.3, 134.0) divmod:把除数和取余结合起来: >>> divmod(10,3) (3, 1) >...

Visual Studio高效调试手段与技巧总结(经验分享)

目录 1、对0xCCCCCCCC、0xCDCDCDCD和0xFEEEFEEE等常见异常值的辨识度 2、在Debug下遇到报错弹框,点击重试,查看函数调用堆栈...

Python自动化脚本代码如何写 - 编程语言

这篇文章主要讲解了“Python自动化脚本代码如何写”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python自动化脚本代码如何写”吧! 1、自动化阅读网页新闻这个脚本能够实现从网页中抓取文...

NIO学习demo

package io_test; import java.io.File; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;...

lua学习笔记---表(数组)

这里的表可以看成一个数据类型,与C语言中的数组有的一拼。 但是相对于C语言的数组来说比较灵活,它的下标和元素都很随意,下标不限于整型(0,1,2,3…),元素也可能不是一直的一个数据类型,它的元素其中也能是整型,浮点型等。形式1> 先创建一个空表;然后初始化表Lua 5.1.4 Copyr...