我自己理解的事件循环,如果不对,请多指教🙇
作为一个前端,整天和浏览器打交道,在浏览器中运行着html
,css
,js
三驾马车,得稍微懂一点点他们的运行原理和运行顺序,今天做一个事件循环记录,面试之前看一看
以下为个人理解,不一定准确 以下为个人理解,不一定准确 以下为个人理解,不一定准确
浏览器的进程
什么是进程
进程是计算机中正在运行的程序实例。它包含了程序的代码、数据、执行状态等信息,并被操作系统视为一个独立的执行单元,具有独立的内存空间和系统资源(如CPU、内存、I/O设备等)。
每个进程都有一个唯一的标识符(PID),用于区分不同的进程。多个进程可以同时运行在计算机中,彼此之间相互独立,互不干扰。
进程是操作系统调度和管理的基本单位,操作系统通过分配CPU时间片、内存空间、I/O资源等来控制进程的执行和交互。常见的操作系统如Windows、Linux、macOS等都使用进程作为管理应用程序的基本机制。
🤖来自 chat-gpt
个人理解: 程序运行需要一个内存空间,这个内存空间有你定义的变量,函数等...,内存空间相互独立,不能使用别的进程中的变量
原因应该是如果一个进程崩溃了,不影响其他进程
什么是线程
线程是进程内的一个独立执行单元,它与同一进程中的其他线程共享该进程的代码、数据和系统资源。线程通常被认为是轻量级的进程,因为创建和销毁线程所需的资源比进程要少得多。
每个线程都有自己的执行状态、堆栈、程序计数器等信息,并且可以独立地执行代码。通过在同一进程中的不同线程之间进行切换,操作系统可以实现并发执行,从而提高系统的性能和响应能力。
由于多个线程共享同一进程的资源,如内存空间和打开的文件等,因此线程之间的通信和同步比进程更加方便和高效。线程通常用于实现并行计算、图形界面交互、网络通信等任务,在现代操作系统和编程语言中都得到了广泛的支持。
🤖来自 chat-gpt
有了自己一个独立的空间后,就可以在空间做自己的事了
个人理解:在空间中做事的程序就是线程,线程可以独立执行代码, 一个进程中最少有一个线程,多个线程可以使用同一个进程的资源
浏览器的线程/进程
浏览器是⼀个多进程多线程的应用程序
在 windows 中 使用 shift + esc
查看 浏览器中的进程
其中最主要的进程有
- 浏览器进程
主要负责界⾯显示、⽤户交互、⼦进程管理等。浏览器进程内部会启动多个线程处理不同的任务。
- ⽹络进程
负责加载⽹络资源。⽹络进程内部会启动多个线程来处理不同的⽹络任务。
- 渲染进程
渲染进程启动后,会开启⼀个渲染主线程,主线程负责执⾏ HTML、CSS、JS 代码。 默认情况下,浏览器会为每个标签⻚开启⼀个新的渲染进程,以保证不同的标签⻚之间不相互影响
渲染主线程
渲染主线程是浏览器中最繁忙的线程,需要它处理的任务包括但不限于
- 解析 HTML
- 解析 CSS
- 计算样式
- 布局
- 处理图层
- 执⾏全局 JS 代码
- 执⾏事件处理函数
- 执⾏计时器的回调函数
📓 为什么渲染进程不用多个线程处理这些事情?
因为 html,css,事件处理函数都是处理一个页面,如果使用多个线程。当处理同一个
dom
的时候,肯可能就不是当时的 dom,位置和大小可能发生不好预料的变化,破坏了渲染
要处理这么多的任务,主线程要怎么处理顺序呢?
⽐如:
-
执⾏⼀个 JS 函数,执⾏到⼀半的时候⽤户点击了按钮,我该⽴即 去执⾏点击事件的处理函数吗?
-
执⾏⼀个 JS 函数,执⾏到⼀半的时候某个计时器到达了时间,我该⽴即去执⾏它的回调吗?
-
浏览器进程通知我“⽤户点击了按钮”,与此同时,某个计时器也到达了时间,我应该处理哪⼀个呢?
所以要有执行顺序,不能随意执行,任务要排队
-
在最开始的时候,渲染主线程会进入一个无限循环
-
每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。
-
其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务
🌰 举个例子 食堂买饭,如果没有人买饭,打饭阿姨就休息,如果有人买饭,就要排队,阿姨给先给第一个人打饭,第一个拿着饭离开,第二个人变成队头,阿姨继续给他打饭。直到没人排队买饭....
整个过程,被称之为 事件循环(消息循环)
异步
但是有一些任务,没法立即处理, 比如
- 计时完成后需要执行的任务 —— setTimeout 、 setInterval
- ⽹络通信完成后需要执⾏的任务 -- XHR 、 Fetch
- ⽤户操作后需要执⾏的任务 -- addEventListener
🌰 就像买饭的时候,到你打饭了,你说得到到我男朋友到了才能打饭,我要和他一起吃,由于你不离开队头,这个时候就会造成堵塞
如果让渲染主线程等待这些任务的时机达到,就会导致主线程⻓期处于「阻塞」的状态,从⽽导致浏览器「卡死」
比如下图👇的定时器,如果其他任务乖乖排队等待 「定时器」 结束,后面代码才可以运行的话,那会严重的影响渲染
🔥 因此,浏览器选择异步来解决这个问题
渲染进程遇到了定时器,ok,开一个计时线程,一边自己计时去,不要影响其他代码执行,等到计时结束了,ok,再把回调函数在插入队列中
🌰 你不是要等你你男朋友来才打饭吗?好的, 去一边等,别影响其他同学,啥时候你男朋友来了,你们再重新排队
📓 那么如何理解 JS 的异步?
浏览器的渲染线程只有一个,渲染线程有很多要处理的事情,如果使用同步的方式,极有可能造成主线程阻塞,导致其他消息队列的其他任务无法执行,这样的结果是,一方面主线程白白浪费时间,另一方面页面无法更新,给用户造成卡死现象
浏览器使用异步方式避免阻塞,当一些不确定什么时间可以执行完毕的任务时,比如
计时器
,网络
,事件监听
等,主线程将任务给其他线程处理,继续执行后续代码,当其他线程 完成时,将回调函数加入到消息队列的末尾,等待执行
JS阻碍渲染
请看代码
<h1>Mr.Yuan is awesome!</h1>
<button>change</button>
<script>
var h1 = document.querySelector('h1');
var btn = document.querySelector('button');
// 死循环指定的时间
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
btn.onclick = function () {
h1.textContent = '袁老师很帅!';
delay(3000);
};
</script>
页面需要等待 3s
后才会把 h1
的结果更改掉,原因很简单,JS执行
, HTML
和CSS的解析
和渲染
是在同一个线程中进行的, 渲染要等 js 执行完毕才会渲染
任务有优先级吗?
任务没有优先级,在消息队列中先进先出
但消息队列是有优先级的
根据 W3C 的最新解释:
-
每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。
-
在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
-
浏览器必须准备好一个微队列,而且微队列中的任务优先所有其他任务执行
🌰 比如还是在食堂打饭 学生们 站一队,老师们站一队,学校领导站一队 来了一个学生,他去学生的那一队列,老师来了,去老师的那一个队列 由于只有一个窗口. 那肯定要学校领导要优先打饭
这么做的原因是 随着浏览器的复杂度急剧提升,使用宏任务过于笼统。 例如我们上文说的,定时器和点击事件同时触发,谁先执行,简单的使用宏任务就无法区分,所以还要细分
在目前 chrome 的实现中,至少包含了下面的队列:
- 微队列:用户存放需要最快执行的任务,优先级「最高」
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」
- 延时队列:用于存放计时器到达后的回调任务,优先级「中」 (延时也不在乎再多等一会,但是如果交互没有立即响应的话,用户感觉不太好)
添加任务到微队列的主要方式主要是使用 Promise、MutationObserver
// 立即把一个函数添加到微队列 Promise.resolve().then(函数)
面试题
问下面的代码执行顺序是什么?
async function async1() {
console.log('async1 start');
await async2()
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
首先,我们知道,Promise.resolve().then(函数) 会产生一个微任务,而且微队列中的任务优先所有其他任务执行
然后 setTimeout
会被放到定时线程中,时间到达之后才能把回调函数放入到主线程中执行
所以简单的分析一下
📓 简单阐述一下JS 的 事件循环 事件循环⼜叫做消息循环,是浏览器渲染主线程的⼯作⽅式。
每次循环从消息队列中取出第⼀个任务执⾏,⽽其他线程只需要在合适的时候将任务加⼊到 队列末尾即可。
过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的浏览器环境,取而代之的是⼀种更加灵活多变的处理方式。
根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级 但浏览器必须有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。
结束
事件循环机制是JavaScript异步编程的核心之一,它通过将异步任务添加到任务队列中,等待合适的时机执行,这样减少页面的阻塞时长.
总之,合理地利用事件循环机制和其他异步API和技术,可以大大提高程序的效率和响应速度,为用户提供更好的体验和服务。
我自己理解的事件循环,如果不对,请多指教🙇
转载自:https://juejin.cn/post/7232552216376295484