客户端容器(浏览器) | 青训营笔记
1.浏览器架构
1.1 浏览器架构变迁
- 单进程架构:所有模块运行在同一个进程里,包含网络、插件、JavaScript 运行环境等
- 多进程架构:主进程、网络进程、渲染进程、GPU 进程、插件进程
- 面向服务架构:将原来的 UI、数据库、文件、设备、网络等,作为一个独立的基础服务
1.2 浏览器各架构对比
1.3 多进程架构介绍
2.渲染进程
2.1 渲染进程多线程架构
内部是多线程实现,主要负责页面渲染,脚本执行,事件处理,网络请求等
2.2 JS 引擎 VS 渲染引擎
1.解析执行 JS
2.XML 解析生成渲染树,显示在屏幕
3.桥接方式通信
两个引擎是相互独立的,如果通过 js 执行 dom 操作,本质上要通过 bridge 操作,所以会产生一定的延迟。
2.3 多线程工作流程
- 网络线程负责加载网页资源
- JS 引擎解析 JS 脚本并且执行
- JS 解析引擎空闲时,渲染线程立即工作
- 用户交互、定时器操作等产生回调函数,放入任务队列中
- 事件线程进行事件循环,将队列里的任务取出交给 JS 引擎执行
3.chrome 运行原理
- 浏览器地址输入 URL 后发生了什么
一道经典的八股文,这里做一个简版。 例如当我们在 web 浏览器的地址栏中输入:www.juejin.cn,具体发生了: 对www.juejin.cn这个网址进行DNS域名解析,得到对应的IP地址
1.根据这个 IP,找到对应的服务器,发起 TCP 的三次握手
2.建立 TCP 连接后, 发起 HTTP 请求
3.服务器响应 HTTP 请求,浏览器得到 html 代码
4.浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)
5.服务器响应对应的资源
6.响应数据完毕, 四次挥手,关闭 TCP 连接 7.浏览器对页面进行渲染呈现给用户
- 其中主要分成了浏览器主进程和渲染进程,下面就是两个进程的详细流程。
3.1 主进程工作流程
3.1.1 输入处理
- 用户 Url 框输入内容的后,UI 线程会判断输入的是一个 URL 地址呢,还是一个 query 查询条件
- 如果是 URL,直接请求站点资源
- 如果是 query,将输入发送给搜索引擎
3.1.2 开始导航
- 当用户按下回车,UI 线程通知网络线程 发起一个网络请求,来获取站点内容
- 请求过程中,tab 处于 loading 状态
3.1.3 读取相应
- 网络线程接收到 HTTP 响应后,先检查响应头的媒体类型(MIME Type)
- 如果响应主体是一个 HTML 文件,浏览器将内容交给渲染进程处理
- 如果拿到的是其他类型文件,比如 Zip、exe 等,则交给下载管理器处理
3.1.4 寻找渲染进程
- 网络线程做完所有检查后,会告知主进程数据已准备完毕,主进程确认后为这个站点寻找一个渲染进程
- 主进程通过 IPC 消息告知渲染进程去处理本次导航
- 渲染进程开始接收数据并告知主进程自己已开始处理,导航结束,进入文档加载阶段
3.2 渲染进程工作流程
3.2.1 资源加载
- 收到主进程的消息后,开始加载 HTML 文档
- 除此之外,还需要加载子资源,比如一些图片,CSS 样式文件以 JavaScript 脚本
3.2.2 构建渲染树
- 构建 DOM 树,将 HTML 文本转化成浏览器能够理解的结构
- 构建 CSSOM 树,浏览器同样不认识 CSS,需要将 CSS 代码转化为可理解的 CSSOM
- 构建渲染树,渲染树是 DOM 树和 CSSOM 树的结合
3.2.3 页面布局
- 根据渲染树计算每个节点的位置和大小
- 在浏览器页面区域绘制元素边框
- 遍历渲染树,将元素以盒模型的形式写入文档流
3.2.2 页面绘制
- 构建图层:为特定的节点生成专用图层
- 绘制图层:一个图层分成很多绘制指令,然后将这些指令按顺序组成一个绘制列表,交给合成线程
- 合成线程接收指令生成图块
- 栅格线程将图块进行栅格化
- 展示在屏幕
3.3 浏览器性能优化
3.3.1 前端性能 performance
- 时间都花在哪
我们发现对于 script 的解析花了 653ms,解析只花了 37ms,绘制更短了,而 idle(等待时间)花费了 484ms。
- 什么情况下卡顿
图中上方黄色的部分即为卡顿的部分,应为在解析 script 的过程,如果有解析时间很长的地方就会发生卡顿
3.3.2 常见的优化手段
- 首屏优化
- 压缩、分包、删除无用代码
- 静态资源分离
- JS 脚本非阻塞加载
- 缓存策略
- SSR
- 预置 loading、骨架屏
这是一些常用的首屏优化的方案,现在基本都是单页面应用,尽量使用分包,利用浏览器并行的能力。静态资源最好使用分离的形式,如果同源的情况下请求,往往携带 cookie,但是使用了 CDN 进行分发也可以节省一定的性能。JS 脚本使用非阻塞或者 body 底部,这样不会卡顿。将 css js 等利用浏览器生成 contenthash,进行强缓存,第二次进入页面就会很快。当然使用服务端渲染和预置一些 loading 和骨架屏也可以实现首屏优化的效果。
- 渲染优化
- GPU 加速
- 减少回流、重绘
- 离屏渲染
- 懒加载
transform 和 opacity 效果均可以触发 GPU 加速,考虑到某几个元素使用 GPU 加速的时候也会新生成一个图层,这样也是消耗性能的,所以会在 css 中设置一个 will-change 属性,will-change 可以设置为 opacity、transform、top、left、bottom、right。告诉浏览器这个元素有可能会被 GPU 加速,浏览器会把这个元素放到新建的图层里面。尽量少用 table 布局,这个内容有一个 table 发生改变都会引起回流,即使是现在主流的框架中的 table 也基本上都是使用的 div 实现的。离屏渲染会另外开辟一些内存,提前加载资源,这样动画会流畅一些。
- JS 优化
- 防止内存泄漏
- 循环尽早 break
- 合理使用闭包
- 减少 Dom 访问
- 防抖、节流
- Web Workers
防止内存泄漏:使用全局变量的时候,会有内存泄漏的风险。Dom 赋值给了 js 变量,Dom 被清除了,但是 js 变量没有被清除,这个时候 Dom 就会继续存在,并且占用内存很大,我们需要手动删除掉。还有一些定时器,如果没有清除定时器的话,也会导致内存泄漏,可以在组件销毁的时候清除定时器。 尽量减少 Dom 的访问次数,因为 dom 的访问是要通过 bridge 的,比较费时间。比如访问 css 的时候,最好使用 cssText 直接替换,或者用 className 的形式,这样只会访问一次,如果一个 Dom 经常使用的话,可以使用一个变量缓存起来,不要每次都去查询。
4.跨端容器
4.1 跨端的由来
- 为什么需要跨端
- 开发成本、效率
- 一致性体验
- 前端开发生态
4.2 常见的跨端方案
4.2.1 跨端方案
- webview
- 小程序
- RN/WeeX
- Lynx
- Flutte
4.2.2 跨端容器
- webview
- Webview,即网页视图,用于加载网页 Url,并展示其内容的控件
- 可以内嵌在移动端 App 内,实现前端混合开发,大多数混合框架都是基于 Webview 的二次开发;比如 UNI-APP、Cordova
- webview 优势
- 一次开发,处处使用,学习成本低
- 随时发布,即时更新,不用下载安装包
- 移动设备性能不断提升,性能有保障
- 通过 JSBridge 和原生系统交互,实现复杂功能
-
webview 使用原生能力
-
JavaScript 调用 Native
- API 注入:Native 获取 Javascript 环境上下文,对其挂载的对象或者方法进行拦截
- 使用 Webview URL Scheme 跳转拦截
- IOS 上 window.webkit.messageHandler 直接通信
- Native 调用 JavaScript
- 直接通过 webview 暴露的 API 执行 JS 代码
- IOS webview.stringByEvaluatingJavaScriptFromString
- Android webview.evaluateJavascrip
- webview<->Native 通信
- JS 环境中提供通信的 JSBridge
- Native 端提供 SDK 响应 JSBridge 发出的调用
- 前端和客户端分别实现对应功能模块
- 小程序
- 微信、支付宝、百度小程序、小米直达号
- 渲染层-webview
- 双线程,多 webview 架构
- 数据通信,Native 转发
- React Native/Weex
- 原生组件渲染
- React/Vue 框架
- virtual dom
- JSBridge
- Lynx
- Vue
- JS Core / V8
- JSBinding
- Native UI / Ski
- Flutter
- wideget
- dart vm
- skia 图形库
- 通用原理
- UI 组件
- 渲染引擎
- 逻辑控制引擎
- 通信桥梁
- 底层 API 抹平表现差
- dart vm
- skia 图形库