浏览器架构
架构演进
- 单进程架构: 所有模块运行在同一个进程里,包含网络、插件、JavaScript运行环境等
- 多进程架构: 主进程、网络进程、渲染进程、GPU进程、插件进程
- 面向服务架构: 将原来的UI、数据库、文件、设备、网络等,作为一个独立的基础服务
架构对比
任务管理器
多进程分工
渲染进程
常见浏览器内核
多线程架构
内部是多线程实现,主要负责页面渲染,脚本执行,事件处理,网络请求等
JS引擎 VS 渲染引擎
- 解析执行JS
- XML解析生成渲染树,显示在屏幕
- 桥接方式通信
渲染进程-多线程工作流程
- 网络线程负责加载网页资源
- JS引擎解析JS脚本并且执行
- JS解析引擎空闲时,渲染线程立即工作
- 用户交互、定时器操作等产生回调函数放入任务队列中
- 将队列里的任事件线程进行事件循环,务取出交给JS引擎执行
例子
const now = Date.now()
setTimeout(() => {
console.log('time10',Date.now() - now) //输出? ?
},10)
setTimeout(() => {
console.log('time30',Date.now() - now) //输出? ?
},30)
while (true) {
if (Date.now() - now >= 20) {
break
}
}
console.log(Date.now() - now) //输出? ?
答案
Chrome 运行原理
如何展示网页
输入处理
- 用户Url框输入内容的后,UI线程会判断输入的是一个URL地址呢,还是一个query查询条件
- 如果是URL,直接请求站点资源
- 如果是query,将输入发送给搜索引擎
开始导航
- 当用户按下回车,UI线程通知网络线程发起一个网络请求,来获取站点内容
- 请求过程中,tab处于loading状态
读取响应
- 网终线程接收到HTTP响应后,先检查响应头的媒体类型 (MIME Type)
- 如果响应主体是一个HTML文件,浏览器将内容交给渲染进程处理
- 如果拿到的是其他类型文件,比如Zip.exe等,则交给下载管理器处理
寻找渲染进程
- 网络线程做完所有检查后,会告知主进程数据已准备完毕,主进程确认后为这个站点寻找一个渲染进程
- 主进程通过IPC消息告知渲染进程去处理本次导航
- 渲染进程开始接收数据并告知主进程自己已开始处理,导航结束,进入文档加载阶段
资源加载
- 收到主进程的消息后,开始加载HTML文档
- 除此之外,还需要加载子资源,比如一些图片,CSS样式文件以及JavaScript脚本
构建渲染树
- 构建DOM树,将HTML文本转化成浏览器能够理解的结构
- 构建CSSOM树,浏览器同样不认识CSS,需要将CSS代码转化为可理解的CSSOM
- 构建渲染树,渲染树是DOM树和CSSOM树的结合
页面布局
- 根据渲染树计算每个节点的位置和大小
- 在浏览器页面区域绘制元素边框
- 遍历渲染树,将元素以盒模型的形式写入文档流
页面绘制
- 构建图层:为特定的节点生成专用图层
- 绘制图层:一个图层分成很多绘制指令,然后将这些指令按顺序组成一个绘制列表,交给合成线程
- 合成线程接收指令生成图块
- 栅格线程将图块进行栅格化
- 展示在屏幕上
前端性能performance
首屏优化
- 压缩、分包、删除无用代码
- 静态资源分离
- JS脚本非阻塞加载
- 缓存策略
- SSR
- 预置loading、骨架屏
渲染优化
- GPU加速
- 减少回流、重绘
- 离屏渲染
- 懒加载
JS优化
- 防止内存泄漏
- 循环尽早break
- 合理使用闭包
- 减少Dom访问
- 防抖、节流
- Web Workers
跨端容器
为什么需要跨端
- 开发成本、效率
- 一致性体验
- 前端开发生态
跨端方案
WebView
- Webview,即网页视图,用于加载网页Url,并展示其内容的控件
- 可以内嵌在移动端App内,实现前端混合开发,大多数混合框架都是基于Webview的二次开发;比如 lonic、Cordova
常用WebView分类
使用WebView优势
- 一次开发,处处使用,学习成本低
- 随时发布,即时更新,不用下载安装包
- 移动设备性能不断提升,性能有保障
- 通过JSBridge和原生系统交互,实现复杂功能
使用原生能力
Javascript 调用Native
- API注入: Native获取Javascript环境上下文,对其挂载的对象或者方法进行拦截
- 使用Webview URL Scheme 跳转拦截
- IOS上 window.webkit.messageHandler 直接通信
Native 调用 Javascript
- 直接通过webview 暴露的 API 执行JS代码
- IOS webview.stringByEvaluatingJavaScriptFromString
- Android webview.evaluateJavascript
WebView<->Native 通信
- JS环境中提供通信的JSBridge
- Native 端提供 SDK 响应JSBridge 发出的调用
- 前端和客户端分别实现对应功能模块
实现一个简易JSBridge
小程序
- 微信、支付宝、百度小程序、小米直达号
- 渲染层-webview
- 双线程,多webview架构
- 数据通信,Native转发
React Native/WeeX
- 原生组件渲染
- React/Vue框架
- virtual dom
- JSBridge
Lynx
- Vue
- JS Core / V8
- JSBinding
- Native Ul / Skia
Flutter
- wideget
- dart vm
- skia图形库
跨端容器通用原理
- UI组件
- 渲染引擎
- 逻辑控制引擎
- 通信桥梁
- 底层API抹平表现差异
跨端方案对比(仅个人意见)
总结
通过这节课学习,我对客户端容器有了更好的理解,不过我对其中优化前端部分比较感兴趣,所以我去掘金网站上继续找了些资料,下面是我获取到对重绘重排的知识的理解
重绘重排
- HTML网页被HTML解析器(HTML Parser)解析生成DOM树
- css被css解析器解析生成CSSDOM树
- 结合DOM树和CSSDOM树,生成一棵渲染树(Render Tree)
- 生成布局(layout),即定位坐标和大小,是否换行,各种position, overflow, z-index属性 ……,将所有渲染树的所有节点进行平面合成
- 将布局绘制在屏幕上(Painting)
重排:当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排(回流)
重绘:- 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘
常见引起重排的属性和方法
- 添加或者删除可见的DOM元素
- 元素尺寸改变——边距、填充、边框、宽度和高度
- 内容变化,如在input框中输入文字
- 浏览器窗口尺寸改变——resize事件发生时
- 计算 offsetWidth 和 offsetHeight 属性
- 设置 style 属性的值
- 激活伪类,如 :hover
"重绘"不一定会出现"重排","重排"必然会出现"重绘"
优化
分离读写操作
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
这段代码会触发4次重排+重绘,因为在console中请求的这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联,因为队列中可能有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
还是上面触发4次重排+重绘的代码,这次只触发了一次重排:
在第一个 console 的时候,浏览器把之前上面四个写操作的渲染队列都给清空了。剩下的 console ,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已
样式集中改变
- 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的
className或csstext
// bad
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
// good
div.className += " theclassname";
div.style.cssText += "; left: " + left + "px; top: " + top + "px;";
离线改变dom
- 隐藏要操作的dom
在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘(触发两次重排,display的隐藏和显示各一,适用对dom进行触发超过两次重排的多次修改)
position属性为absolute或fixed
减小重排开销,不用考虑对其他元素的影响
优化动画
- 牺牲平滑 适当牺牲平滑,换取速度,自己权衡 比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多
- 启用GPU加速
- GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率
- GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)
/*
* 根据上面的结论
* 将 2d transform 换成 3d
* 就可以强制开启 GPU 加速
* 提高动画性能
*/
div {
transform: translate3d(10px, 10px, 0);
}
参考:浏览器重绘(repaint)重排(reflow)与优化浏览器机制 - 掘金 (juejin.cn)
DocumentFragment - Web API 接口参考 | MDN (mozilla.org)
浏览器的渲染原理简介 | 酷 壳 - CoolShell