浏览器工作原理
文章
- V8 JavaScript engine - V8官网
- Navigation timing - MDN
- 渲染页面:浏览器的工作原理 - MDN
- 关键渲染路径 - MDN
- 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 - 博客文章
- 图解浏览器渲染原理及流程 - 51CTO文章
- 渲染树的形成原理你真的很懂吗? - 微信公众号文章
- 深入浅出浏览器渲染原理 - 知乎
- 深入理解 JavaScript 的 V8 引擎 - 文章
- How V8 JavaScript engine works step by step [with diagram] - 英文文章
- CSS 匹配规则顺序是怎么样的? - 前端面试题宝典
- 浏览器一帧都会干些什么? - 前端面试题宝典
从输入 URL 到页面加载发生了什么?
- 肝完《浏览器基本原理与实践》,我总结了这些 - SegmentFault文章
- 浏览器工作原理与实践目录 - GitHub
- 从 Chrome 源码的角度带你看看从输入 URL 到页面显示背后的故事。。一万五千字 - 掘金
- 前端经典面试题: 从输入 URL 到页面加载发生了什么? - SegmentFault文章
主要流程
- 生成请求行
- 查找强缓存
- DNS 解析
- 建立 TCP 连接
- 发送 HTTP 请求
- 接收 HTTP 响应
- 浏览器解析/渲染页面
- 连接结束
进程、线程
浏览器是多进程的,进程可以包括多个线程。
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引擎执行。
WARNING
注意,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加载会造成堵塞吗? - 微信公众号文章
结论:
- css加载 不会阻塞DOM树的解析、会阻塞DOM树的渲染
- css加载 不会堵塞JS加载、会堵塞JS运行
- JS的下载和解析 会阻塞 DOM 和 CSS 的解析和渲染
WebWorker
创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)。
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)。
回流、重绘
- 怎么理解回流跟重绘?什么场景下会触发? - 前端面试题宝典
- 浏览器的回流与重绘 (Reflow & Repaint) - 掘金
- 回流与重绘:CSS 性能让 JavaScript 变慢? - 张鑫旭
回流必将引起重绘,重绘不一定会引起回流
回流/重排 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 ⌛️
- Background Tasks API - MDN
内存管理
- Memory management - MDN
- 使用 Chrome Devtools 分析内存问题 - 博客
- Chrome DevTools - Memory - Chrome开发者官网文档
内存生命周期:分配、使用(读写)、释放
垃圾回收
- 「硬核 JS」你真的了解垃圾回收机制吗 - 掘金
1.标记清除
“对象是否可以获得”。
此算法分为 标记 和 清除 两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成1
- 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
2.引用计数
“对象有没有其他对象引用到它”。
如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
- 声明一个变量,赋予它一个引用值时,计数+1;
- 同一个值被赋予另外一个变量时,引用+1;
- 保存对该值引用的变量被其他值覆盖,引用-1;
- 引用为0,回收内存;
内存泄漏
- 意外的全局变量
- 被遗忘的计时器、回调函数
- 闭包
- 没有清理的DOM元素引用、监听事件