zhangdizhangdi

浏览器工作原理

文章

从输入 URL 到页面加载发生了什么?

主要流程
  • 生成请求行
  • 查找强缓存
  • DNS 解析
  • 建立 TCP 连接
  • 发送 HTTP 请求
  • 接收 HTTP 响应
  • 浏览器解析/渲染页面
  • 连接结束

performance navigation timestamps
图:performance navigation timestamps

进程、线程

浏览器是多进程的,进程可以包括多个线程。

JS是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。

进程

  • 浏览器主进程
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等❓
  • 网络进程❓
  • 浏览器渲染进程
    • 内部是多线程
    • 每打开一个新网页就会创建一个进程
    • 主要用于页面渲染,脚本执行,事件处理等。
  • GPU 进程

    用于 3D 绘制等,最多一个。

  • 第三方插件进程

    每种类型的插件对应一个进程,仅当使用该插件时才创建。

浏览器内核(渲染进程)

  • GUI 渲染线程
    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
  • JS 引擎线程
    • 也称为JS内核,负责处理JS脚本程序。(例如V8引擎)
    • JS引擎线程负责解析JS脚本,运行代码。
    • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
  • 事件触发线程
    • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
    • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
  • 定时触发器线程
    • 传说中的 setInterval 与 setTimeout 所在线程
    • 浏览器定时计数器并不是由JS引擎计数的,(因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  • 异步 http 请求线程
    • 在 XMLHttpRequest 连接后通过浏览器新开一个线程请求
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JS引擎执行。

注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

GUI 渲染线程会尽可能早的将内容呈现到屏幕上,并不会等到所有的 HTML 都解析完成之后再去构建和布局 Render Tree,而是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

渲染流程

  • DOM树构建

    渲染引擎使用HTML解析器(调用XML解析器)解析HTML文档,将各个HTML元素逐个转化成DOM节点,从而生成DOM树。

  • CSSOM树构建

    CSS解析器解析CSS,并将其转化为CSS对象,将这些CSS对象组装起来,构建CSSOM树。

  • 渲染树(Render Tree)构建

    DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两棵树构建出一棵渲染树。

  • 页面布局(Layout)

    渲染树构建完毕之后,元素的位置关系以及需要应用的样式就确定了,这时浏览器会计算出所有元素的大小和绝对位置。

  • 页面绘制(Paint)

    页面布局完成之后,浏览器会将根据处理出来的结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。

阻塞问题

结论:

  • css加载 不会阻塞DOM树的解析、会阻塞DOM树的渲染
  • css加载 不会堵塞JS加载、会堵塞JS运行
  • JS的下载和解析 会阻塞 DOM 和 CSS 的解析和渲染

WebWorker

创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)。
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)。

回流、重绘

回流必将引起重绘,重绘不一定会引起回流

回流/重排 Reflow

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代)
  • 激活CSS伪类(例如::hover)
  • 获取一些特定属性的值和方法
    • offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
    • scrollIntoView()、scrollIntoViewIfNeeded()
    • getComputedStyle()
    • getBoundingClientRect()
    • scrollTo()

重绘 Repaint

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

GPU 硬件加速

硬件加速就是将浏览器的渲染过程交给GPU处理,而不是使用自带的比较慢的渲染器。
我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能。

可以开启GPU
  • 3D transforms: translate3d, translateZ 等
  • <video> <canvas> <iframe>
  • transform
  • opacity
  • position: fixed
  • will-change
  • filter

事件循环

requestIdleCallback ⌛️

内存管理

内存生命周期:分配、使用(读写)、释放

垃圾回收

1.标记清除

“对象是否可以获得”。

此算法分为 标记 和 清除 两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。

这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

  • 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
  • 然后从各个根对象开始遍历,把不是垃圾的节点改成1
  • 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
  • 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收

2.引用计数

“对象有没有其他对象引用到它”。
如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

  • 声明一个变量,赋予它一个引用值时,计数+1;
  • 同一个值被赋予另外一个变量时,引用+1;
  • 保存对该值引用的变量被其他值覆盖,引用-1;
  • 引用为0,回收内存;

内存泄漏

  • 意外的全局变量
  • 被遗忘的计时器、回调函数
  • 闭包
  • 没有清理的DOM元素引用、监听事件

浏览器页面生命周期