JS运行机制及事件循环机制

进程:独立运行,拥有资源空间的应用程序
线程:CPU调度的最小单位

浏览器: 多进程

浏览器有哪些进程?

  • Browser进程,也是主进程

    • 负责各个页面的管理 创建 销毁
    • 前进后退等
    • 网络资源下载
  • 插件进程:比如Chrome的各种插件

  • GPU进程

    • 复杂3D的绘制
  • 渲染进程(浏览器内核)

    • 负责页面的渲染
    • 脚本处理、事件执行等
  • 网络进程

    • 网络资源的加载

渲染进程

  • GUI渲染线程
    • 渲染浏览器界面
    • 解析html dom
    • 解析css
    • 结合渲染树(rendering tree)
    • 重绘(repaint):当元素样式的改变不影响页面布局时,比如元素的颜色,浏览器将对元素进行的更新,称之为重绘
    • 回流(reflow):也叫做重排。当元素的尺寸或者位置发生了变化,就需要重新计算渲染树,这就是回流,比如元素的宽高、位置,浏览器会重新渲染页面,称为回流,又叫重排(layout)

注意: GUI线程和JS引擎线程是互斥的,同一时间只能执行一个

  • JS 引擎线程
    • 解析js脚本、运行js代码
    • 任务队列
  • 事件触发线程
    • 控制事件循环 (task queue)
  • 定时触发线程
    • setTimeout setInterval
    • 浏览器定时计数器 不是 js 引擎计数
  • 异步请求线程
    • XMLHttpRequest 回调

事件循环 Eventloop

  • js 同步任务 和异步任务 同步任务主线程执行 形成执行栈
  • 事件触发线程 任务队列
  • 任务队列(Task)、微任务队列(Microtask)和宏任务队列(Macrotask)

浏览器事件循环

在这里插入图片描述

任务队列

任务队列(Task)、微任务队列(Microtask)和宏任务队列(Macrotask)

宏(普通)任务(macrotask)

可以将每次执行栈执行的代码当做是一个宏任务
宏任务(任务执行栈) =》 GUI 渲染 =》 宏任务(任务执行栈)

  • I/O(Input/Output)
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame等
微任务(microtask)

当宏任务(任务执行栈)执行完,会在渲染前,将执行期间所产生的所有微任务都执行完
宏任务(任务执行栈) =》 微任务 =》 GUI 渲染 =》 宏任务(下一个任务执行栈) =》GUI 渲染

  • process.nextTick
  • MutationObserver
  • Promise.then catch finally

整体流程:

  1. 执行一个当前执行栈同步任务(栈中没有就从事件队列中获取)
  2. 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  3. 宏任务内的同步任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  4. 开始检查渲染,然后GUI线程接管渲染
  5. 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

事件循环机制

  1. 代码执行时,先执行同步任务,然后将异步任务放入任务队列中,等待执行。
  2. 当所有同步任务执行完毕后,JavaScript引擎会去读取任务队列中的任务。
  3. 将队列中的第一个任务压入执行栈中执行,执行完毕后将其出栈。
  4. 如此循环执行,直到任务队列中的所有任务都执行完毕。

在这里插入图片描述

Node 事件循环机制

在这里插入图片描述

为什么Nodejs 中任务队列要分阶段?

  • 为了有效地管理和调度各种类型的异步任务,确保事件循环的高效运行。这种设计使得 Node.js 能够在单线程环境中有效地处理大量的并发任务

如果一个阶段中有产生了新的当前阶段的微任务,是在当前事件循环中执行吗?还是到下一次事件循环执行?

  • 它将会在当前的事件循环中执行。事件循环的每个阶段都有自己的执行栈,微任务会在当前阶段的执行栈完成后执行

这个不是分阶段吗?那肯定是一个阶段内部的队列执行完毕之后,在执行下一个阶段的微任务队列,不也是阻塞的吗?

  • Node.js是基于事件循环和非阻塞I/O模型的,这意味着在其执行过程中,会有多个阶段,但是并没有严格意义上的“阶”的概念
与浏览器事件循环的差异:

Node.js 的事件循环(Event Loop)是一个处理异步操作的机制,它会按照顺序依次执行不同阶段任务。事件循环机制中分为多个阶段,每个阶段都有自己的任务队列,包括:

  1. Timers 阶段:
  • 处理 setTimeout 和 setInterval 调度的回调函数。
  • 如果指定的时间到了,回调函数会被放入这个队列。
  1. Pending Callbacks 阶段:
  • 处理一些 I/O 操作的回调,比如 TCP 错误类型的回调。
  • 这些回调并不完全由开发者控制,而是由操作系统调度的。
  1. idle, Prepare 阶段:
  • 仅供内部使用的阶段。
  1. Poll 阶段:
  • 获取新的 I/O 事件,执行 I/O 回调函数。
  • 通常情况下,这个阶段会一直等待,直到有新的 I/O 事件到来。
  1. Check 阶段:
  • 处理 setImmediate 调度的回调函数。
  • setImmediate 的回调会在这个阶段执行,比 setTimeout 更早。
  1. Close Callbacks 阶段:
  • 处理一些关闭的回调函数,比如 socket.on(‘close’, …)。
多个队列的必要性

不同类型的异步任务有不同的优先级和处理方式。使用多个队列可以确保这些任务被正确地调度和执行:

  1. Timers 和 Poll 阶段的区别:
  • setTimeout 和 setInterval 的回调在 Timers 阶段执行,这些回调函数依赖于计时器的到期时间。
  • Poll 阶段处理大多数 I/O 回调,这是事件循环的主要阶段,处理大部分异步 I/O 操作。
  1. Immediate 与 Timeout 的不同:
  • setImmediate 的回调函数在 Check 阶段执行,这是在当前事件循环周期结束后立即执行。
  • setTimeout 的回调函数则是在 Timers 阶段执行,它可能会延迟到下一个事件循环周期,甚至更久。
  1. 处理关闭回调:
  • Close Callbacks 阶段专门处理如 socket.on(‘close’) 这样的回调,以确保在资源释放时执行。
// example:
const fs = require('fs');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

setImmediate(() => {
  console.log('setImmediate');
});

fs.readFile(__filename, () => {
  console.log('I/O callback');
  
  setTimeout(() => {
    console.log('setTimeout inside I/O');
  }, 0);
  
  setImmediate(() => {
    console.log('setImmediate inside I/O');
  });
});

console.log('Synchronous log');

// 执行结果
Synchronous log
setImmediate
I/O callback
setImmediate inside I/O
setTimeout inside I/O
setTimeout
  • Synchronous log 立即执行。
  • setImmediate 回调会在 setTimeout 之前执行,因为 setImmediate 在当前循环结束后立即执行。
  • I/O callback 在读取文件完成后执行(Poll 阶段)。
  • 在 I/O 回调中,setImmediate 会先于 setTimeout 执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值