【总结】浏览器内容全梳理

167 阅读13分钟

内容总结自 浏览器工作原理 这门课程,将重点的有收获的内容总结如下,同时发现了一个不花钱的镜像网站 here,这个文章非常不错的,适合多次阅读。

一、从输入url到展示出来页面经历过什么?

  1. 浏览器对输入内容进行判断

    • 如果是url,则发起网络请求
    • 否则,使用浏览器默认的搜索引擎拼接搜索结果
  2. 浏览器发起网络请求,查看本地缓存是否有,有的话直接返回,没有则向服务器发起请求

  3. 进行dns解析

    • 检查浏览器缓存
    • 检查操作系统缓存,如host文件
    • 检查路由器缓存
    • 如果都没有,向DNS服务器查询
    • 根服务器返回顶级域名服务器地址
    • 顶级服务器返回次级服务器地址
    • 次级服务器通过域名查找到ip
    • local dns server缓存结果,并返回给用户,缓存在系统中
  4. 和服务器创建tcp链接,如果是https还要建立tls链接

  5. 构造请求头、请求行等信息,把域名相关cookie附加到请求头中,发送请求信息

  6. 服务器返回相应数据

  7. 浏览器接受到数据后,解析响应头,

    • 如果状态码是301(永久重定向),302(临时),则重定向到其他url,从location字段里读取重定向地址,发起新的http或者https请求,一切重头开始
    • 如果是200,则进行相应数据处理,根据content-type字段区分是什么类型,如果是text/html,则进行html解析
  8. 准备渲染进程:如果是同一站点的网页共用渲染进程,否则新建渲染进程

  9. 渲染进程

    • 构建dom树:解析html构造dom树:html是边下载边解析
    • 样式计算:解析css构造cssom树
      • css构造会阻塞下面的布局树生成,因此适合放在头部,css的下载不会阻塞dom树解析
      • js如果在头部会阻塞dom的解析,js下载和执行都会阻塞,所以js要放在底部,而且js可能会操作dom,所以也是要放到底部的
      • css可能会阻塞dom渲染:当头部有js脚本的时候,css会阻塞dom树构建。因为js脚本执行之前是需要把它之前的css编译执行完的,因为js可能也会修改css,所以头部如果有脚本,会间接造成css对dom解析的阻塞,css在这种情况下也会阻塞dom渲染。
    • 布局:根据dom树和cssom树生成布局树,包含元素位置信息,不含display none、head、script标签,同时计算样式,为每个节点找到对应的样式,并且需要计算出来每个元素的位置。
    • 分层:对布局树进行分层,生成分层树,是为了z-index,position,滚动条等复杂的分层操作。
    • 绘制:为每个分层生成绘制列表,提交到合成线程
    • 光栅化:合成线程将图层分成图块,光栅化线程中转成位图,分块是为了让渲染一块一块出来,这样节省性能。
    • 合成:合成线程发送绘制图块命令到浏览器线程
    • 显示:浏览器线程生成页面

从url请求到显示.png

二、性能优化方式

网络方面

  1. 减少请求个数

    • js和css内敛
    • js如果不需要操作dom,则改成async或者defer操作
    • css添加媒体查询减少下载量
    • css放在头部,js放在底部或者异步加载
  2. 减小资源的大小

    • 压缩css和js,移除注释内容
  3. 减少请求时间

    • 静态资源放cdn
    • 使用http2
  4. 图片优化

    • css代替图画
    • 图片裁剪
    • 小图片打包成base64
    • 使用字体图标
    • 选择争取的图片格式,webp等,svg等
  5. webpack

    • 压缩代码
    • cdn加速
    • tree shaking
    • code split:按需加载
    • splitChunksPlugin来提取公共模块,利用浏览器缓存无需长期变动的公共代码

渲染流程

  1. 减少重绘与重排

    • js减少对样式的操作,能用css用css
    • 避免逐条改变样式,用类名改变样式
    • dom尽量少操作,用createDocumentFragment完成整块dom操作后再添加到文档中
    • dom离线,先设置成none,设置好后再现实出来
    • 避免同步布局,不要在操作dom之后立马查询dom的属性,这样会导致同步布局,因为正常的流程是js一个task,之后样式重新布局会到另一个task中,如果立刻读取的话,需要马上布局,造成同步布局,影响性能。
    • 利用节流和防抖
    • 利用css合成动画,可以将有动画的元素添加will-change属性,是 告诉渲染引擎需要将该元素单独生成一个图层
    • 动画的元素设置postion是absulte或者fixed
    • 不要使用table布局,改动一个属性可能会使table整个布局
  2. 减少js脚本执行时间

    • 将任务拆解,分成多个小任务
    • web worker
  3. 懒加载

image.png

三、浏览器垃圾回收策略

  • 栈内存回收:通过下移ESP来销毁函数保存在栈中的执行上下文。如果有新的函数调用的话会直接覆盖这段新的内容。

  • 堆内存回收

    • 垃圾回收的基础:代际假说

      • 第一个是大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问;
      • 第二个是不死的对象,会活得更久。
    • V8垃圾回收

      • 把堆分为新生代和老生代两个区域,新生代存放的是生存时间短的对象,老生代存放的是生存时间久的对象。新生代通常只支持1-8m的容量。
      • 新生代垃圾回收:副垃圾回收器(Scavenge算法,划分新老区域)
      • 老生代垃圾回收:主垃圾回收器
    • 垃圾回收算法公共流程

        1. 标记空间中活动对象和非活动对象
        1. 回收非活动对象所占据的内存
        1. 内存整理:频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片。当内存中出现了大量的内存碎片之后,如果需要分配较大连续内存的时候,就有可能出现内存不足的情况。所以最后一步需要整理这些内存碎片,但这步其实是可选的
    • 副垃圾回收器(新生代-Scavenge算法)

      • Scavenge算法:将新生代空间分为两个区域:对象区域、空闲区域。新加入的对象存入对象区域,当对象区域写满时,需要执行一次垃圾清理操作。
      • 垃圾回收中,对对象区域垃圾做标记,标记完进入清理阶段,副垃圾回收器把存活的对象复制到空闲区域中,同时有序的排列起来。
      • 复制完成后,对对象区域与空闲区域进行角色翻转,将原来的对象区域变成空闲区域,原来的空闲区域变成对象区域。角色反转让新生代的这两块区域无限重复使用下去。
      • 对象晋升策略:经过两次垃圾回收依然存活的对象,会被移动到老生区中
    • 主垃圾回收器:标记-整理算法

      • 大的对象直接分配到老生区,对象占用空间大,对象存活时间长。

      • 标记阶段:从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。

      • 整理阶段:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

      • 增量标记算法:为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成。

垃圾回收.png

四、浏览器的组成,包含哪几个部分

  1. 浏览器进程:负责界面展示、用户交互、子进程管理,同时提供存储功能
  2. 渲染进程:包含浏览器内核和js引擎
    • 浏览器内核:排版引擎blink
    • js引擎v8
  3. GPU进程
  4. 网络进程:负责页面的网络资源加载
  5. 插件进程

五、http1、http2、http3

http1

http1.0

最重要是是引入了请求头和响应头,支持多种类型文件传输。

  1. 引入了请求头和响应头
// 期望返回的服务类型
accept: text/html
// 期望服务器采用的压缩方式
accept-encoding: gzip, deflate, br
// 期望返回的文件编码
accept-Charset: ISO-8859-1,utf-8
// 期望页面语言:中文
accept-language: zh-CN,zh
  1. 引入了响应头状态码

  2. 提供了cache机制,缓存已下载过的数据

  3. 加入了用户代理

http1.1

最重要的是引入了持久链接keep-alive

  • keep-alive:HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。目前浏览器中对于同一个域名,默认允许同时建立 6 个 TCP 持久连接。
    • http对头阻塞:持久连接虽然能减少 TCP 的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求。
  • 虚拟主机的支持:HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。
  • 动态内容的支持:HTTP/1.1 通过引入 Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。
  • cookie支持

http2

http1.1的问题

  1. TCP的慢启动:一旦一个 TCP 连接建立之后,就进入了发送数据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。

  2. 多条TCP连接,会竞争固定的宽带

  3. HTTP1.1队头阻塞问题

http2的优化

HTTP/2 的思路就是一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个 TCP 连接竞争带宽所带来的问题。

多路复用是通过在协议栈中添加二进制分帧层来实现的,有了二进制分帧层还能够实现请求的优先级、服务器推送、头部压缩等特性,从而大大提升了文件传输效率。

  1. 多路复用:添加了一个二进制分帧层,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。

image.png

  1. 设置请求的优先级
  2. 服务器推送:HTTP/2 还可以直接将数据提前推送到浏览器。服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器。
  3. 头部压缩

http3

http2问题

  1. TCP队头阻塞:如果在数据传输的过程中,有一个数据因为网络故障或者其他原因而丢包了,那么整个 TCP 的连接就会处于暂停状态,需要等待丢失的数据包被重新传输过来。

在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。

当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好。

  1. TCP建立连接的延时:TCP建立连接需要1.5个RTT,如果要建立TLS链接,大概需要2-3个RTT,所以在传输数据之前,需要花费3-4个RTT。

  2. TCP协议僵化:中间设备的僵化、操作系统僵化。如果我们在客户端升级了 TCP 协议,但是当新协议的数据包经过这些中间设备时,它们可能不理解包的内容,于是这些数据就会被丢弃掉。这就是中间设备僵化,它是阻碍 TCP 更新的一大障碍。

QUIC(http3)

HTTP/3 选择了一个折中的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议。

image.png

  1. 实现了类似TCP的流量控制、传输可靠性的功能。
  2. 集成了TLS加密功能:使用了TLS1.3
  3. 实现了http2的多路复用功能。
  4. 实现了快速握手的功能:因为是基于UDP的,所以QUIC可以实现使用0RTT或者1RTT来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。

http3挑战

  1. 服务器和浏览器没有对http3提供比较完整的支持。
  2. 部署http3存在问题,因为系统内核对UDP的优化远远没有达到TCP的优化程度。
  3. 中间设备僵化问题。

http.png

六、浏览器安全

【总结】网络安全相关内容脑图总结

七、webpack优化

如何提高打包速度

  1. 优化loader:babel-loader排除node_modules,用include锁定编译范围,开启缓存loader: 'babel-loader?cacheDirectory=true'
  2. happypack:webpack正常打包是单线程的,加上happyPack可以使loader变为并行执行
  3. DllPlugin:将特定的类库提前打包然后引入,一般用于类库打包
  4. 代码压缩
  5. resolve.alias: 别名方式映射路径,更快找到文件

如何减少webpack打包体积

  1. 按需加载
  2. 作用域提升
  3. tree shaking

【总结】从4个方面重新梳理webpack