浏览器那些事

151 阅读7分钟

进程和线程傻傻分不清楚

只要记住js是单线程的,线程(js)是运行在浏览器这个进程环境中的更小单位。

进程是cpu资源分配的最小单位。

线程是运行在进程基础上的一次程序执行的最小单位,进程可以包含多个进程。

image.png

多进程的浏览器

浏览器包含的进程:

1.主进程

只有一个,负责协调和控制。主要作用如下:

  • 浏览器的界面展示和交互,比如网页前进和后退
  • 控制新建和关闭网页的创建进程和销毁进程
  • 负责网页内容的下载和管理等

2.插件进程

每个插件对应一个进程,只有在插件使用的时候才会存在。

3.渲染进程

是绘制页面的主要进程,包含多个线程,比如js加载、事件处理、定时器等

4.GPU进程

负责3D绘制

多进程浏览器的优点

  • 防止多个tab、插件崩溃,影响整个浏览器的稳定性

  • 效率更高

  • 但是也会浪费更多的资源,时间换空间

重点分析渲染进程

渲染进程是对于前端同学来说最熟悉的,也是最重要的,包含js解析执行、事件循环、异步事件等多个线程。

  • GUI渲染线程

主要负责页面的布局和绘制。

解析HTML结构,构成DOM树,解析css,构成css ROM树,最后合并成render树

当页面需要重绘和回流的时候,也是GUI线程起作用。

  • 定时器线程

之所以不是js线程自己计时,是因为js本身是单线程的,计时就不能执行其他任务,并且计时本身是异步任务,这个时候执行其他同步任务会影响计时的精确性(最主要还是js忙不过来,需要浏览器帮他)。

原理:浏览器单独的线程用来计时,当计时结束后,会将回调函数push进事件队列,等待被执行。

  • js引擎线程

负责解析和执行js代码

  • 事件处理线程

当js运行到事件处理代码时,会将事件处理交给浏览器的事件处理线程,会在事件发生时(比如点击之后)将回调函数放在js任务队列中等待执行。

  • 异步线程

异步请求新开线程单独处理

事件循环

1611938ba2b4b9f4_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.image

js引擎线程

执行url请求时,会经历以下过程:

URL解析

输入地址进行解析、是否合法、自动补充、安全性检查、是否有缓存

知识点1:js文件代码每次都会变吗?那怎么实现缓存?

知识点2:为什么HTML不支持缓存?

知识点3:URL组成部分?

DNS查询(将url解析成ip地址)

查询缓存顺序:

浏览器dns缓存

查询host

操作系统dns缓存

运营商dns缓存

没有缓存的情况下,将会经历以下过程:

截屏2023-01-23 上午9.45.53.png

  • 主机向本地DNS服务器发送DNS查询报文(包含想要查询的地址a.b.com)
  • 本地DNS服务器将报文转发到根DNS服务器
  • 根服务器发现一级域名是com,就会返回一级域名所在的ip地址列表给本地DNS服务器(你可以问下com这个服务器)
  • 本地DNS服务器向其中一个发送查询报文,同理会返回b.com所在的ip地址列表(你去问问b.com)
  • 本地DNS服务器向其中一个b.com发送查询报文,这才找到a.b.com所在的ip地址,并且返回给本地DNS服务器
  • 由此主机获得了ip地址,就可以向该ip地址发送请求。

算法体现:

整个过程用到了递归迭代两种算法。

主机向本地DNS服务器发送请求,后面的过程都是由本地DNS来完成,并且结果也是从本地DNS获取到,是一个由大问题转化为小问题的过程,是递归的过程。

后面的由本地DNS向各个服务器查询的过程是迭代的过程,都是由本地DNS发起,在本地DNS获取到结果结束。

注:DNS查询不一定全部遵循这个过程,也有可能只有迭代过程或者只有递归过程

下面这个过程就是只存在递归的过程:

截屏2023-01-23 上午10.05.13.png

TCP链接(与ip地址所在的服务器建立连接)

三次握手和四次挥手

目的是为了在客户端和服务端之间建立可靠的链接,之所以使用三次握手,是为了双方都确认对方的发送、接收能力正常。

知识点1:三次握手作用

浏览器发送请求、服务器处理请求

浏览器接受响应

包括根据不同的响应头中的状态码去做不同的事。

对资源进行缓存等

这里存在一个知识点:关于浏览器主进程和浏览器渲染进程之间的通信

浏览器主进程接收到响应之后,开启一个下载线程下载资源。

将下载完的资源传输给浏览器渲染进程

浏览器渲染进程开始渲染

页面渲染

接下来开始GUI线程的表演

  • 解析HTML,转换成DOM树
  1. 解码 通过utf解码将二进制文件解码成HTML文件
  2. 预解析 提前加载资源 比如img的src
  3. 词法分析和构建DOM树 同时进行
  4. 完成后,触发DOMContentLoaded事件
  • 解析CSS,转换成css ROM树
  • 结合DOM和CSS ROM树生成render渲染树
  • 对元素位置、样式信息等进行计算
  • GUI线程将渲染信息传输给GPU,由GPU进程将页面绘制在浏览器上
  • 触发load事件

解析HTML的时候遇到js文件,会执行js,DOM构建暂停(js执行会阻碍页面渲染)

如果js引用了未加载的css文件,也不会执行js文件(css会阻碍js加载)

最好将css放在最前面,js放在最后面

重绘和回流 回流是指元素位置等发生变化时,会重新解析HTML,重新渲染一遍,代价较大 重绘则代价较小,只是在元素颜色等发生变化时,只局部更新

js解析执行

接下来是js引擎线程的表演时刻

GUI线程渲染结束后,将执行权交给js引擎线程,开始解析并执行js代码。

截屏2023-01-23 上午10.53.25.png

  • 语法分析阶段
  1. 词法分析 将代码分成单个词法单元
  2. 生成AST 将词法单元转换成抽象语法树
  3. 转成成机器语言
  • 预编译阶段
  1. 变量提升、函数声明提升
  2. 确定各个执行上下文对象的this
  3. 确定作用域和作用域链
  • 执行代码

js代码的执行过程是单线程的,但是还有辅助它执行的其他线程,共同组成事件循环机制(由事件触发线程管理)

  1. js引擎执行的同步任务放在任务栈
  2. 当遇到异步任务、定时器任务或者事件触发任务等,浏览器会在事件发生时将回调函数push到一个任务队列中(由事件触发线程管理)
  3. 当js引擎执行完同步任务之后,会在任务队列中拿出一个任务添加到任务栈中执行
  4. 所有在任务栈中执行的任务都被称为宏任务

执行顺序为:宏任务->渲染->宏任务

es6中,这里涉及到一个名词:微任务(最常见的微任务就是promise,是由js引擎实现的,跟浏览器无关)

在执行宏任务期间积攒的所有微任务,都会在该宏任务执行结束后立即执行所有微任务,然后进行渲染,再执行下一个宏任务(同步任务或者任务队列中取一个)

最终执行顺序为:宏任务->所有微任务->渲染->宏任务

补充:setInterval和setTimeout的区别

setInterval和setTimeout实现的setInterval有什么区别呢?

从事件循环机制上来说,setInterval是确切的每隔time就push一个回调到任务队列,被阻碍的情况下会一下子执行多次。

而setTimeout是在前一个回调执行之后,再间隔至少time才push一个新的回调到任务队列,不存在一下子执行多次的结果。

所以还是推荐用setTimeout实现setInterval。


function myInterval(cb,time) { 
  function fn() { 
    let timer;
    cb();
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn();
    }, time);
  }

  setTimeout(() => {
    fn();
  }, time);
}