JavaScript基础之EventLoop事件循环机制

每每遇到异步问题的时候,总是能让我们抓耳挠腮,无所适从。那么我们就来讲讲异步问题——EventLoop事件循环机制。首先来了解一个概念,进程和线程:

进程:进程是 CPU 资源分配的最小单位;

线程:线程是 CPU 调度的最小单位

如何理解这句话呢?计算机是多进程执行的,打开自己的电脑的任务管理器,可以看到所有的执行的进程,而每一个执行的进程又是有多个线程组成的。线程是一个进程中代码的不同执行路线。一个进程的内存空间是共享的,每个线程都可用这些共享内存。而每个进程是独立的。

所以这里就会出现另一个问题:多进程和多线程的问题。

多进程:进程之前是相互独立的,是互相不影响的。我们打开电脑,听着歌,敲着代码,互不干扰,这就是多进程。

多线程:一个进程是由多个线程组成的,也就是说,想要完成一件事情,需要分多步来做。例如,当打开一个网页的时候,可能需要解析js,css,html,需要将他们渲染到页面上,需要发起网络请求等,这就是一个个线程。当完成了他们自己的使命,那么线程也就被销毁了。

所以我们要讲的就是关于异步或者eventloop就是关于线程的相关内容。有了进程和线程的了解,我们就该让js在线程中运行起来。那么js的运行,其实是要分俩步走的。

  • 编译并执行 JavaScript 代码,完成内存分配、垃圾回收等;(执行引擎)
  • 为 JavaScript 提供一些对象或机制,使它能够与外界交互。(执行环境)

chrome和node都提供了v8执行引擎,但是他们的执行环境不一样。接下来,我们再来讲三个概念:

是一种数据结构,是利用完全二叉树维护的一组数据,分为两种,一种为最大,一种为最小堆,将根节点最大叫做最大堆大根堆,根节点最小叫做最小堆小根堆线性数据结构,相当于一维数组,有唯一后继。

在计算机科学中是限定仅在表尾进行插入删除操作的线性表。 是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底最后的数据栈顶,需要读数据的时候从栈顶开始弹出数据是只能在某一端插入删除特殊线性表

队列:特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。 队列中没有元素时,称为空队列

队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出。

ok,准备知识就说到这里,我们接入正题:eventLoop;

我们知道,在 JavaScript 运行的时候,JavaScript Engine 会创建和维护相应的堆(Heap)和栈(Stack),同时通过 JavaScript 的执行环境提供的一系列 API(例如 setTimeout、XMLHttpRequest 等)来完成各种各样的任务。

JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事情。在 JavaScript 的运行过程中,真正负责执行 JavaScript 代码的始终只有一个线程,通常被称为主线程,各种任务都会用排队的方式来同步执行。这种方式最常见的一个问题就是:如果你尝试执行一段非常耗时的同步代码,浏览器就没办法同时去渲染 GUI,导致界面失去响应,也就是被阻塞了。然而 JavaScript 却又是一个非阻塞(Non-blocking)、异步(Asynchronous)、并发式(Concurrent)的编程语言,这就得说说 JavaScript 的事件循环(Event Loop)机制了。

事件循环(Event Loop) 是让 JavaScript 做到既是单线程,又绝对不会阻塞的核心机制,也是 JavaScript 并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制。一句话,Event Loop 只不过是实现异步的一种机制而已。

Event Loop 分为两种,一种存在于 Browsing Context 中,还有一种在 Worker 中。

  1. Browsing Context 是指一种用来将 Document 展现给用户的环境。例如浏览器中的 tab,window 或 iframe 等,通常都包含 Browsing Context。
  2. Worker 是指一种独立于 UI 脚本,可在后台执行脚本的 API。常用来在后台处理一些计算密集型的任务。

我们重点讲解Browsing Context.另外,Event Loop 并不是在 ECMAScript 标准中定义的,而是在 HTML 标准中定义的。在 JavaScript 执行环境中(以 V8 为例),只是实现了 ECMAScript 标准,而并不关心什么 Event Loop。也就是说 Event Loop 是属于 JavaScript 的执行环境 的,是由宿主环境提供的(比如浏览器)。这也是为什么浏览器和node中的EventLoop是有区别的。所以千万不要搞错了,这也是前面介绍 JavaScript 执行引擎 和 执行环境 的原因。

EventLoop中的任务队列

在执行和协调各种任务时,Event Loop 会维护自己的任务队列。任务队列又分为 异步队列 和 同步队列 两种。

异步队列

一个 Event Loop 会有一个或多个 异步队列,这是一个先进先出(FIFO)的有序列表,存放着来自不同任务源的任务。HTML标准规定:用户交互,ajax请求,鼠标、键盘事件,数据操作,setTimeout,setInterval等等都属于异步源,所有来自这些 都会被放到对应的异步任务队列中等待处理。

注意:

  1. 来自相同 异步任务源的 任务,必须放在同一个 异步队列中;
  2. 来自不同异步任务源的 Task,可以放在不同的 异步队列 中;
  3. 同一个 异步队列内的 Task 是按顺序执行的;
  4. 但对于不同的 异步队列(Task Source),浏览器会进行调度,允许优先执行来自特定 异步源的 Task。

同步任务队列

同步任务队列和异步队列类似,也是一个存放不同任务任务源任务的有序列表,所不同的是一个EventLoop只会有一个同步任务队列。HTML标准中并没有具体规定哪些是同步任务,但是一般以类似于promise,MutationObserver等不是异步任务的都是同步任务。

JavaScript运行环境的执行机制

1. 主线程不断循环;

2. 对于同步任务,创建执行上下文(Execution Context),按顺序进入执行栈;

3. 对于异步任务

  • 与步骤 2 相同,同步执行这段代码;
  • 将相应的 Task(或 Microtask)添加到 Event Loop 的任务队列;
  • 由其他线程来执行具体的异步操作。
其他线程是指:尽管 JavaScript 是单线程的,但浏览器内核是多线程的,它会将 GUI 渲染、定时器触发、HTTP 请求等工作交给专门的线程来处理。

另外,在 Node.js 中,异步操作会优先由 OS 或第三方系统提供的 异步接口来执行,然后才由线程池处理。

4. 当主线程执行完当前执行栈中的所有任务,就会去读取 Event Loop 的任务队列,取出并执行任务;

5. 重复以上步骤。

大概是这样的:

Event Loop 处理模型

EventLoop作为处理事件循环每次的循环也是相当的复杂。主要是分为三个步骤:

  1. 执行异步任务:从异步队列中取出最老的一个 Task 并执行;如果没有 Task,直接跳过。
  2. 执行 同步任务:遍历同步任务队列并执行所有同步任务(参考 Perform a microtask checkpoint)。
  3. 进入 Update the rendering(更新渲染)阶段
    1. 设置 Performance API 中 now() 的返回值。Performance API 属于 W3C High Resolution Time API 的一部分,用于前端性能测量,能够细粒度的测量首次渲染、首次渲染内容等的各项绘制指标,是前端性能追踪的重要技术手段,感兴趣的同学可关注。
    2. 遍历本次 Event Loop 相关的 Documents,执行更新渲染。在迭代执行过程中,浏览器会根据各种因素判断是否要跳过本次更新。
    3. 当浏览器确认继续本次更新后,处理更新渲染相关工作:
      1. 触发各种事件:Resize、Scroll、Media Queries、CSS Animations、Fullscreen API。
      2. 执行 animation frame callbacks,window.requestAnimationFrame 就在这里。
      3. 更新 intersection observations,也就是 Intersection Observer API(可用于图片懒加载)。更新渲染和 UI,将最终结果提交到界面上。

至此,Event Loop 的一次循环结束:

同步任务的执行机制

同步任务会在第 2 步时被执行。实际上按照 HTML 标准,在以下几种情况中同步任务都会被执行:

  1. 某个 Task 执行完毕时(即上述情况)。
  2. 进入脚本执行(Calling scripts)的清理阶段(Clean up after running script)时。
  3. 创建和插入节点时。
  4. 解析 XML 文档时。

同时,在当前 Event Loop 轮次中动态添加进来的同步任务,也会在本次 Event Loop 循环中全部执行完(上图其实已经画出来了)。

最后一定要注意的是,执行同步任务是有前提的:当前执行栈必须为空,且没有正在运行的执行上下文。否则,就必须等到执行栈中的任务全部执行完毕,才能开始执行同步任务。JavaScript 会确保当前执行的同步代码不会被同步任务打断。

就写这么多吧,有遗漏,以后再有补充。。。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值