事件循环
==单线程是异步产生的原因,事件循环是异步实现的方式==!!超大声
浏览器的进程模型
这里提到了两个概念,浏览器,进程
我们首先来了解一下,浏览器是什么
从计算机科学的角度来看,浏览器是一种网络客户端软件,它实现了多种网络协议和标准,允许用户通过图形用户界面(GUI)访问和交互互联网上的资源。在客户端-服务器(Client-Server, CS)架构中,浏览器作为客户端,向服务器发送请求并接收服务器响应的内容。
大致分为两种模式 C/S B/S
C/S 又称客户服务器模式,在传统的客户端-服务器架构中,客户端通常是一个专用的软件应用程序,它与服务器的通信是通过特定的协议进行的。客户端负责发送请求给服务器,服务器处理请求并返回响应。客户端和服务器之间的交互可以是同步的也可以是异步的
B/S又称浏览器-服务器架构,也被称为Web架构,是一种特殊的客户端-服务器架构。在这种架构中,客户端的主要形式是Web浏览器,它通过HTTP/HTTPS协议与服务器通信。浏览器负责解释和渲染服务器发送的HTML、CSS和JavaScript,以展示给用户
在了解了CS和BS架构的基础上,我们可以更深入地理解浏览器是什么。
浏览器是一种软件应用程序,它充当用户与互联网之间的桥梁。它是浏览和交互Web内容的工具,同时也是实现BS架构中客户端功能的核心。浏览器的主要作用是请求、解析和显示Web服务器上的信息,这些信息通常是以HTML、CSS和JavaScript编写的。
浏览器的核心功能包括:
- 请求和接收数据:浏览器通过HTTP/HTTPS协议与Web服务器通信,请求页面和数据,并接收服务器响应的内容。
- 内容渲染:浏览器解析HTML和CSS,将文本、图片、视频等元素按照一定的布局展示给用户。
- 用户交互:用户可以通过浏览器与Web页面交互,如点击链接、填写表单、播放媒体等。
- 脚本执行:浏览器内置JavaScript解释器,可以执行网页中的JavaScript代码,实现动态功能和用户交互。
- 数据存储:浏览器提供本地存储机制,如cookies、localStorage等,以保存用户数据和提高网页加载速度。
- 安全和隐私:现代浏览器注重用户的安全和隐私保护,包括加密通信、防止恶意软件、保护个人信息等。
浏览器的工作流程:
- 用户输入URL:用户在浏览器的地址栏输入网址或搜索关键词。
- 域名解析:浏览器将域名解析为IP地址,以找到目标服务器。
- 建立连接:浏览器与服务器建立TCP连接,通常使用HTTP/HTTPS的默认端口。
- 发送请求:浏览器发送HTTP请求,请求可以是获取网页、图片、视频等资源。
- 服务器响应:服务器处理请求并返回HTTP响应,包含请求的资源或错误信息。
- 内容渲染:浏览器解析服务器返回的HTML、CSS和JavaScript,将其渲染为可视化的网页内容。
- 用户交互:用户可以与渲染后的网页进行交互,如点击按钮、填写表单等。
浏览器是互联网时代最重要的工具之一,它不仅允许我们访问信息,还提供了一个平台,使我们能够通过Web应用程序进行各种在线活动。随着技术的发展,浏览器也在不断地进化,支持更多的功能和标准,以提供更安全、更快速、更便捷的网络体验。
市面上常见的浏览器大概有以下几种
浏览器 | 内核(渲染引擎) | JavaScript 引擎 |
---|---|---|
Chrome | Blink(新) Webkit(旧) | V8 |
FireFox | Gecko | SpiderMonkey |
Safari | Webkit | JavaScriptCore |
Edge | EdgeHTML | Chakra |
IE | Trident | Chakra |
PhantomJS | Webkit | JavaScriptCore |
Node.js | 无 | V8 |
通常我们开发使用的都是Chrome,同时也与基于V8引擎的node息息相关
进程
我们知道,程序的运行都需要一段指定的内存空间,例如,每个程序的执行都是在内存中进行的,有人可能要问,我们的软件不是安装在硬盘之中吗,为什么cpu不直接去硬盘里运行呢?这里涉及计算机组成原理。
我们都知道,cpu的运行速度非常非常的快,而硬盘的速度相比cpu可能都无法达到百分之一,于是产生了介于二者之间的运行内存,他的速度可能是cpu的十分之一,但造价却又十分昂贵,所以出现了
cpu->内存->硬盘,当然,cpu中也存在着一级二级三级缓存,这个速度比内存还要快,但是容量十分有限,笔者cpu还算是稍微强一点的,可以看到三缓只有24mb。具体大家自己了解计算机组成原理
回过来,进程到底是什么呢,我们可以简单的理解为,进程就是下面每个软件所占用的内存空间。每个应用启动的时候至少有一个进程,并且每个进程相互独立。但是他们之前可以相互通信,并非完全独立
线程
提到进程,我们不得不提到线程,再聊浏览器线程之前,我们不妨了解一下cpu的多线程
以笔者cpu为例,笔者cpu为8核心16线程
那么这么多线程有什么用呢
简单来说,一个8核心16线程的CPU意味着每个物理核心能够处理两个线程。这样的设计可以提高CPU的并发处理能力,使得在多任务处理和并行计算方面有更好的性能。总结就几个字,提高多任务的处理能力。
回来,我们说浏览器的线程
有了进程之后,我们就需要考虑谁来运行程序呢?上面我们提到,进程只是简单理解为一块内存空间,那么谁在执行呢?
就是我们的线程,一个进程至少存在一个线程,进程开始时创立的一个线程叫做主线程,如果进程需要同时处理多个任务,主线程就会启动更多的线程来协助,主线程结束,整个任务结束
那么浏览器有哪些呢
浏览器是一个多进程多线程的应用程序,为了避免浏览器因为某一个功能的崩溃而整体崩溃掉,浏览器成为了多进程的应用
不妨看一下,浏览器的多进程,我们可以看到,在什么都没有打开的情况下,浏览器启动了很多个进程了
浏览器进程:
主要负责界面显示(并非页面展示)、用户交互(事件)、子进程管理(其他进程由他启动)等。浏览器进程内部会启动多个线程处理不同的任务。
网络进程:负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。
渲染进程:(每一个标签页都是一个渲染进程)
渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、CSS、JS 代码。
默认情况下,浏览器会为每个标签页开启一个新的渲染进程,以保证不同的标签页之间不相互影响
在众多线程中,渲染主线程是最为繁忙的一个
渲染主线程要做的事情很多,例如
-
解析 HTML
-
解析 CSS
-
计算样式
-
布局
-
处理图层
-
每秒把页面画 60 次
-
执行全局 JS 代码
-
执行事件处理函数
-
执行计时器的回调函数
-
......
那么既然任务这么多,那么任务先后顺序,主线程又如何处理呢
例如,函数正在执行,此时用户点击了某个按钮,是否是继续执行代码,还是先响应用户的操作呢
下面为主线程执行排队图
w3c中叫:event loop chrome源码中叫 message loop
- 在最开始的时候,渲染主线程会进入一个无限循环,不断的监听消息队列(事件--循环)
- 每一次循环会检查消息队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。
- 其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务
这样一来,就可以让每个任务有条不紊的、持续的进行下去了。
整个过程,被称之为事件循环(消息循环)
异步
在程序运行的时候,难免出现无法立即执行的情况,例如计时器
网络通讯 fetch
时间交互 onClick ,addEventListener
这些都无法立即执行,但是浏览器并不会等待他们,如果浏览器等待他们,那么就可很有可能陷入阻塞的情况,于是乎,异步出现了
使用异步的方式,使得浏览器遇到此类情况,将这些任务压入异步队列,而继续执行主线程
我们来看这样一段代码
js
<h1>测试用的代码</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>
我们可以看到,点击按钮过后,并没有立即发生改变,而是浏览器生生的卡了三秒之后,页面才发生变化,那么为什么呢?明明先执行的是h1的文字更改,为什么不先更改文字,然后再等待三秒呢?
因为js与渲染都在浏览器的渲染主线程之中,要执行js必须停止渲染,进行渲染必须停止js
这也就是为什么,
页面加载之初:主线程通知交互线程,监听用户的操作,用户发生交互后,将回调函数包装成任务放入消息队列
fn执行后,首先回将dom中的文字发生更改,但是并没有立即渲染出来,因为任务还没有执行完毕,但是仍让触发了重绘任务,只不过重绘任务需要排队,放再了fn后面,只有当fn执行完毕后,重绘才会开始执行
这也就是为什么,浏览器生生的卡了三秒后,页面才发生变化
优先级
任务没有优先级!!!
任务没有优先级!!!
任务没有优先级!!!
但消息队列有优先级
根据最新的信息,W3C确实已经不再使用"宏任务"这一术语,而是采用了更加细化的"任务类型"来描述不同的异步任务。这一变化体现了浏览器处理异步任务的更高级和复杂的方法。
在新的模型中,每个任务都被分配了一个特定的类型,同一个类型的任务必须在同一个队列中,而不同类型的任务可以分属于不同的队列。浏览器在一次事件循环中,会根据实际情况从不同的队列中取出任务执行。这种模型允许浏览器根据任务的类型和优先级来更有效地管理异步执行 1 2 3 。
随着浏览器复杂度的提升w3c最新的解释,要摒弃掉宏队列的说法,而转换为
-
每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。 在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
-
浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行
似乎就是再告诉我们,每个任务要划分到各自的任务队列,每个队列优先级不一,但必须要存在一个优先队列:微队列,优先级位于最高,也因此,不同的浏览器出现了不同的差异,但始终有一个队列位于所有队列的最优先处。
那么微队列是什么呢?什么任务会进入微队列之中
最常见的是 promise的then方法,
当然还有一个MutationObserver,
MutationObserver
接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。大家可以去mdn了解一下目前再谷歌浏览器存在以下几个队列
- 延时队列:用于存放计时器到达后的回调任务,优先级为"中"。
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级为"高"。
- 微队列:用于存放需要最快执行的任务,优先级为"最高"。
这些队列的引入,取代了之前简单的宏任务和微任务二分法,使得浏览器能够更精细地控制任务的执行顺序和优先级。这种变化是对现代浏览器日益增长的处理需求的一种适应,也是为了提供更流畅的用户体验和更高效的性能表现。
总的来说,W3C的这种变化是对浏览器事件循环机制的一种改进和细化,使得异步任务的处理更加灵活和高效。
那么又可以解释另一个问题:计时器的计时一定精准吗
js受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此会存在偏差 其次按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差 操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,也就携带了这些偏差
至此,对于事件循环我们已经有了一定的了解,接下来就是一些题目,大家可以用来检测一下
jsfunction a() { console.log(1); Promise.resolve().then(function () { console.log(2); }); } setTimeout(function () { console.log(3); }, 0); Promise.resolve().then(a); console.log(5); 输出 //5 1 2 3 不难看出,首先代码同步执行,遇到了计时器,放入了延时队列,随后遇到了promise.rslove()他会立即执行,但是后面的then方法将会放到微队列里,随后遇到了log(5),直接输出了5 随后微队列和延时队列此时都存在任务 有限执行微队列的任务,运行a方法 a方法中首先执行了输出1,随后又遇到了promise,then方法依旧放入微队列 此时微队列第一个任务执行完毕,此时微队列因为先前的方法又产生了新的任务,所以继续执行微队列的任务 输出了2 此时微队列任务执行完毕,开始执行延时队列,计时器任务,输出了3
怎么样,对事件循环是否又有了新的认识呢?
ps:学习与袁老的大师课,仅作经验分享