JavaScript的并发模型和事件循环机制是什么

本篇内容介绍了“JavaScript的并发模型和事件循环机制是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

"单线程"语言

在浏览器实现中,每个单页都是一个独立进程,其中包含了JS引擎、GUI界面渲染、事件触发、定时触发器、异步HTTP请求等多个线程。

进程(Process)是操作系统CPU等资源分配的最小单位,是程序的执行实体,是线程的容器。线程(Thread)是操作系统能够进行运算调度的最小单位,一条线程指的是进程中一个单一顺序的控制流。

因此我们可以说JS是"单线程"式的语言,代码只能按照单一顺序进行串行执行,并在执行完成前阻塞其他代码。

JS数据结构

JS的几种重要数据结构:

 ● 栈(Stack):用于JS的函数嵌套调用,后进先出,直到栈被清空。

 ● 堆(Heap):用于存储大块数据的内存区域,如对象。

 ● 队列(Queue):用于事件循环机制,先进先出,直到队列为空。

事件循环

我们的经验告诉我们JS是可以并发执行的,比如定时任务、并发AJAX请求,那这些是怎么完成的呢?其实这些都是JS在用单线程模拟多线程完成的。

如上图所示,JS串行执行主线程任务,当遇到异步任务如定时器时,将其放入事件队列中,在主线程任务执行完毕后,再去事件队列中遍历取出队首任务进行执行,直至队列为空。

全部执行完成后,会有主监控进程,持续检测队列是否为空,如果不为空,则继续事件循环。

setTimeout定时任务

定时任务setTimeout(fn, timeout)会先被交给浏览器的定时器模块,等延迟时间到了,再将事件放入到事件队列里,等主线程执行结束后,如果队列中没有其他任务,则会被立即处理,而如果还有没有执行完成的任务,则需要等前面的任务都执行完成才会被执行。因此setTimeout的第2个参数是最少延迟时间,而非等待时间。

当我们预期到一个操作会很繁重耗时又不想阻塞主线程的执行时,会使用立即执行任务:

setTimeout(fn, 0);

特殊场景1:最小延迟为1ms

然而考虑这么一段代码会怎么执行:

setTimeout(()=>{console.log(5)},5)setTimeout(()=>{console.log(4)},4)setTimeout(()=>{console.log(3)},3)setTimeout(()=>{console.log(2)},2)setTimeout(()=>{console.log(1)},1)setTimeout(()=>{console.log(0)},0)

了解完事件队列机制,你的答案应该是0,1,2,3,4,5,然而答案却是1,0,2,3,4,5,这个是因为浏览器的实现机制是最小间隔为1ms。

// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456if (!(after >= 1 && after <= TIMEOUT_MAX))  after = 1; // schedule on next tick, follows browser behavior

浏览器以32位bit来存储延时,如果大于 2^32-1 ms(24.8天),导致溢出会立刻执行。

特殊场景2:最小延迟为4ms

定时器的嵌套调用超过4层时,会导致最小间隔为4ms:

var i=0;function cb() {    console.log(i, new Date().getMilliseconds());    if (i < 20) setTimeout(cb, 0);    i++;}setTimeout(cb, 0);

可以看到前4层也不是标准的立刻执行,在第4层后间隔明显变大到4ms以上:

0 6671 6692 6703 6724 6765 6816 685

Timers can be nested; 然而,后5个这样的嵌套计时器间隔是被迫至少4毫秒。

特殊场景3:浏览器节流

为了优化后台tab的加载占用资源,浏览器对后台未激活的页面中定时器延迟限制为1s。对追踪型脚本,如谷歌分析等,在当前页面,依然是4ms的延时限制,而后台tabs为10s。

setInterval定时任务

此时,我们会知道,setInterval会在每个定时器延时时间到了后,将一个新的事件fn放入事件队列,如果前面的任务执行太久,我们会看到连续的fn事件被执行而感觉不到时间预设间隔。

因此,我们要尽量避免使用setInterval,改用setTimeout来模拟循环定时任务。

JavaScript的并发模型和事件循环机制是什么