详尽 - 输入URL到页面渲染完成发生了什么

615 阅读10分钟

本题是面试中特高频题, 是一个前端er综合素质的展现, 整明白了, 给面试官眼前一亮的感觉吧~

目录

  1. 浏览器进程
  2. DNS解析
  3. TCP连接
  4. HTTP请求
  5. 渲染

1 浏览器进程

浏览器是多进程的, 以 Chrome 为例, Chrome 有 5 个进程, 他们的各自的功能分别是

  • 浏览器进程: 主要负责界面展示(地址栏, 菜单), 用户交互, 子进程管理, 同时提供存储功能
  • 网络进程: 负责页面网络资源的请求, 如 HTTP 请求, websocket 请求
  • GPU(图形处理器)进程: 负责 UI 界面展示
  • 渲染进程: 负责将接受到的 HTML, CSS,js 转化成用户界面
  • 插件进程: 负责插件运行

1.1 浏览器内核(渲染进程)

浏览器内核就是我们说的渲染进程 avatar

    1. GUI 线程
    • 负责渲染浏览器界面, 包括解析 HTML, CSS, 构建 DOM 树, Render 树, 布局与绘制
    1. JS 引擎线程
    • 负责处理所有的 js 脚本
    1. 事件触发线程
    • 用来控制事件循环, 管理事件队列
    1. 定时器线程
    • 当定时器被触发时, 事件会推到事件队列
    1. 异步 HTTP 请求线程
    • 在 XMLHttpRequest 连接后启动的线程
    • 该线程检测到请求结果变更, 会把回调放进事件队列, 等待 JS 引擎空闲后执行

1.2 线程和进程的关系

一个线程有一个或者多个进程, 线程之间共同完成分配下来的任务, 打个比方:

  • 假如一个进程是一个工厂, 有其独立的资源 => 系统分配的内存
  • 工厂之间相互独立 => 进程之间相互独立
  • 工人共同完成任务 => 多个线程通过协作完成任务
  • 工厂有一个或多个工人 => 一个进程至少有一个线程
  • 工人之间共享空间 => 同一进程下的各个线程共享内存(包括代码段, 数据集, 堆等)

进程是 CPU 资源分配的最小单位,是拥有资源和独立运行的最小单位. 线程是 cpu 调度的最小单位, 是一次程序运行单位

1.3 页面展示过程

从用户输入信息到页面展示的不同阶段,是不同的进程在发挥作用,示意图如下: avatar 从图中可以看出, 整个过程需要各个进程之间配合完成, 整体过程如下:

  1. 首先浏览器进程处理用户输入请求信息;
  2. 然后网络进程发送 URL 请求;
  3. 接着浏览器进程开始准备渲染进程
  4. 渲染进程接收数据后, 开始解析和渲染页面

2 DNS 解析

DNS 解析实际上就是寻找你所需要的资源的过程。假设你输入 www.baidu.com,而这个网址并不是百度的真实地址,互联网中每一台机器都有唯一标识的 IP 地址,这个才是关键,但是它不好记,乱七八糟一串数字谁记得住啊,所以就需要一个网址和 IP 地址的转换,也就是 DNS 解析。下面看看具体的解析过程

2.1 具体解析

DNS 解析其实是一个递归的过程

avatar 例如输入 www.baidu.com, 过程为 本地域名服务器 => .(根域名服务器) => .com(com 顶级域名服务器) => baidu.com 域名服务器 => www.baidu.com 域名服务器

2.2 DNS 优化

2.2.1 DNS 缓存

浏览器缓存 => 系统缓存 => 路由器缓存 => IPS 缓存 => 根域名服务器缓存 => 顶级域名服务器缓存 => 域名服务器缓存

  • 浏览器缓存: 输入chrome://dns, 你可以看到chrome浏览器的DNS缓存
  • 系统缓存: 一般存在 hosts 文件中

2.2.2 DNS 负载均衡

当前访问 www.baidu.com 时, 返回的 IP 地址会有不同. 一般大公司的会有成千上百台服务器来支撑访问, DNS 可以返回一台合适机器的 IP 给用户, 具体根据每台机器的负载量, 该机器具体用户的地理位置等因素, 这就是 DNS 的负载均衡

3. 建立 TCP 连接

三次握手

  1. 客户端发送 SYN 标识的数据包
  2. 服务端接受后, 发送 SYN/ACK 标识的数据包(确认客户端的发送数据的能力)
  3. 客户端接受后, 发送 ACK 标识的数据包(确认服务端的接受和发送能力)

四次挥手

  1. 客户端送到断开连接请求, 此时客户端不会发送数据, 但可以接受
  2. 服务端接受后, 不会再接受数据, 但可以发送
  3. 服务端发送完数据后, 告知客户端可以断开连接
  4. 客户端收到后, 告知服务端已经断开连接

4. 发送 http 请求

发送 HTTP 请求的过程, 就是构建请求报文并通过 TCP 协议发送到服务器的指定端口中, 请求报文包括 请求头, 请求行, 请求体

4.1 请求报文

  • 请求行
Method Request-URL HTTP-Version CRLF eg: GET index.html HTTP/1.1

包括 请求方式 url 协议名

  • 请求头 request Header 里面的内容 包括 Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie 等字段

  • 请求体 发送的请求数据

4.2 浏览器缓存

4.2.1 强制缓存

avatar

  • 强缓存规则下, 如果缓存命中 则直接读取缓存数据;
  • 如果缓存未命中, 则请求服务器获取最新数据.
4.2.2 强制缓存规则

对于强制缓存,服务器 response Header 中的两个字段来表示, Expires 和 Cache-Control

**Expires**
Expires值为服务器返回的过期时间,如果下次请求的时间少于此时间, 则直接使用缓存数据. 但是由于服务器时间和客户端时间有误差, 也将导致缓存命中的误差, 另Expires是HTTP1.0的产物, 现在大多数用Cache-Control来替代


**Cache-Control**
- private  客户端可以缓存
- public   客户端和服务端都可以缓存
- no-cache 协商缓存
- no-store 不可以使用缓存
- max-age = t 缓存将在t秒后失效
4.2.3 协商缓存

avatar

  • 协商缓存规则下, 客户端会拿到缓存标识, 向服务端验证是否失效, 有效则直接使用, 无效则向服务器请求新的数据, 并存储最新数据及缓存规则
4.2.4 协商缓存规则
**Last-Modified**
- Last-Modified:服务器在响应请求时,会告诉浏览器资源的最后修改时间
- if-Modified-Since:请求头携带的信息, 服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回304和响应报文头. 如果修改, 返回新的数据


** Etag
- Etag: 服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)
- If-None-Match:再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现If-None-Match则与被请求资源的唯一标识进行对比。
- 如果相同: 说明资源无心修改,则响应header,浏览器直接从缓存中获取数据信息。返回状态码304.
- 如果不同: 说明资源被改动过,则响应整个资源内容,返回状态码200。
- Etag优先级是高于Last-Modifed的,所以服务器会优先验证Etag
4.2.5 缓存优先规则
  • 强缓存优先级高于协商缓存, 若两种缓存皆存在,且强制缓存命中目标,则协商缓存不再验证标识。
4.2.6 from memory cache/ from disk cache
200 form memory cache : 不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。
浏览器关闭后,数据将不存在(资源被释放掉了),再次打开相同的页面时,不会出现from memory cache。

200 from disk cache: 不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,
关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是from disk cache。

优先访问memory cache,其次是disk cache,最后是请求网络资源

5. 渲染过程

5.1 步骤

avatar

  • 解析 HTML 生成 DOM 树
  • 解析 CSS 生成 CSSOM
  • 加载或执行 JavaScript
  • 生成渲染树(Render Tree)
  • 布局
  • 分层
  • 生成绘制列表
  • 光栅化
  • 显示

5.2 加载js defer/sync

  • 相同点: 都可异步加载 js
  • defer: 加载完成后,等待 CSSOM 和 DOM 树构建完后才执行 JavaScript
  • sync : 加载完成后,立即执行, 所以会造成阻塞

5.3 浏览器阻塞情况分析

CSS:

  • CSS 放在 head 中会阻塞页面的渲染(页面的渲染会等到 CSS 加载完成)
  • CSS 阻塞 JS 的执行(js 线程和 GUI 线程互斥)
  • CSS 不阻塞外部脚本的加载(不阻塞加载, 但是阻塞 js 执行, 因为浏览器有预先扫描机制) JS
  • 直接引入的 js 会阻塞页面的渲染(js 线程和 GUI 线程互斥)
  • 异步 js defer 不阻塞页面的解析
  • 异步 js async 下载过程不阻塞页面的解析, 下载完立即执行会阻塞页面的解析
  • js 不阻塞页面资源的加载
  • js 阻塞后续 js 代码的执行(单线程)

补充: DOMContentLoaded(dom 加载完执行) -> onLoad(所有资源加载完执行)

5.4 回流和重绘

5.4.1 回流

  • 定义: 当 render 数中部分或全部元素的尺寸、结构、或某些属性发生改变, 浏览器重新渲染页面的过程叫做回流 会导致回流的操作
  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的 DOM 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法

对应的属性和方法

clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()

5.4.2 重绘

  • 定义: 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

5.4.3 减少回流和重绘

CSS

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。 - 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

JS

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
  • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。