-
本文大约11456字符
-
建议需要一点点前端的知识再进行阅读,至少了解什么是HTML CSS JS
-
阅读本文需要带着3个问题
- 一个浏览器页面是怎么加载出来的?
- 浏览器怎么才能加载的更快?
- 前端代码如何实现跨端?
-
主要参考
00 课程介绍
- 本节课程主要着重介绍浏览器的架构以及运行原理,并以一道八股文为例,讲解在Chrome浏览器里,网页是如何加载并且渲染成我们所见的画面。
- 了解到在浏览器里JS引擎和渲染引擎如何协同工作,如何从多个角度优化前端的性能体验。再以webview容器为扩展,认识一些常见的跨端方案。
01 浏览器架构
1.1 浏览器架构变迁
- 单进程架构
- 早期架构
- 所有模块运行在同一个进程里,包含网络 插件 JS运行环境等
- 多进程架构
- 现代架构
- 主进程 网路进程 渲染进程 CPU进程 插进进程
- 每一个tab都是运行在独立的沙盒中,安全性比较高
- 面向服务架构
- 将原来的UI 数据库 文件 设备 网络等 作为一个独立的基础服务
- 多进程架构的升级版,将一些服务分离出来
- 需要实现定义好接口,从而进行交互
- 下图中 从左到右是上述三个架构图示
1.2 浏览器各架构对比
- 很明显,面向服务的架构在各方面表现最佳,面向服务架构就是为了解决多进程架构的扩展性问题。
- 任务管理器
- 在任务管理器中可以看到一个浏览器下面有很多个进程组成
1.3 多进程工作流程
- 各个进程的工作范围
思考
- 为什么会有单进程架构?
- 因为硬件限制,早期昂贵
- 进程基础框架会占用资源,单进程会节省这部分资源
- 面向服务架构是否会取代多进程架构?
- 长远来看应该是会的
- 适应性好,性能好的设备上使用面向服务架构,性能不好的自动退化到多进程架构
02 渲染进程
2.1 渲染进程多线程架构
常见浏览器内核
- 国产极速模式,本质上是内核切换
渲染进程-多线程架构
- 内核是多线程的实现,主要负责页面渲染、脚本执行、事件处理、网络请求、定时器等。
2.2 JS引擎VS渲染引擎
- JS引擎
- 解析执行 JS(V8将常用的函数翻译成机器码加速)
- 渲染引擎
- XML解析生成渲染树,显示在屏幕
- 两个引擎 采用桥接方式通信,存在一定的通信延迟
2.3 多线程工作流程
- 网络线程负责加载网页资源
- JS引擎解析JS脚本并且执行
- JS引擎空闲时,渲染引擎立即工作
- 用户交互、定时器操作等产生回调函数放入任务队列中
- 事件线程进行事件循环,将队列中的任务取出交给JS引擎去执行
一道笔试题
- 以下代码在浏览器环境下输出顺序 内容
const now = Date.now()
……
// 没学过,之后再写hh
- 解读
- 第一个代码块 10s后将回调函数放入队列
- 第二个代码块 30s后将回调函数放在队列
- 第三个代码块 死循环 同步代码,在20s时退出
- 第四个代码块,打印 now
- 输出
- 先在20s时输出第四个代码块
- 然后立刻去查看队列,在20s左右继续输出第一个代码块
- 然后等到30s 左右,大致一个4ms的轮询延迟,输出第二个代码块
03 Chrome 运行原理
3.1 主进程工作流程
如何展示网页
- 浏览器地址中输入 URL 后发生了什么
- 以下展示细节
输入处理
- 用户URL框输入内容后,UI线程会判断输入的是一个URL地址还是一个query查询条件
- 如果是 URL ,直接请i去站点资源
- 如果是 query ,将输入发送到搜索引擎。
开始导航
- 当用户按下回车,UI线程同志网络线程发起一个网络请求,来获取站点内容
- 请求过程中,tab处于 loading 状态
读取响应
- 网络线程接收到HTTP响应后,先检查响应头的媒体类型(MIME TYPE)
- 如果响应主体是一个 HTML文件,浏览器将内容交给渲染进程处理
- 如果拿到的是其他类型的文件,就执行一个下载逻辑,交给下载管理器去处理
寻找渲染进程
- 网络线程做完所有检查后,会告知主进程数据已经准备完毕,主进程确认之后为这个站点寻找一个渲染进程
- 主进程通过IPC消息告知渲染进程取去处理本次导航
- 渲染进程开始接收数据并告知主进程自己已经开始处理,进入文档加载阶段
寻找到之后就开始进入渲染进程
3.2 渲染进程工作流程
资源加载
- 收到主进程的消息后,开始加载HTML文档
- 除此之外,还需要加载字子元,比如 图片 CSS样式文件 JS脚本
构建渲染树
- 构建 DOM 树,将HTML文本转化为浏览器能够理解的结构,也给JS提供了操作自己的接口
- 构建CSSOM树,同样也为JS提供了操作接口
- 构建渲染树,渲染树是DOM树与CSSOM树的结合
页面布局
- 根据渲染树计算每个节点的位置和大小
- 在浏览器页面区域内绘制元素边框
- 遍历渲染树,将元素以盒模型的形式写入文档流
页面绘制
- 构建图层:为特定的节点生成专用图层
- 绘制图层:一个图层分为很多沪指指令,然后将这些指令按照顺序组成一个绘制列表,交给合成线程
- 合成线程接收命令生成图块-图块-图形实体的总称
- 栅格线程将图块栅格化-转成位图-通常使用GPU加速
- 展现在屏幕上-调用显卡
- 浏览器示例-图层
3.3 浏览器性能优化
前端性能 performance
- 什么地方卡
- 什么时候卡
首屏优化
- 压缩、分包、删除无用代码
- 静态资源分离
- 将 html js css 文件拆分为小文件
- 在CDN服务器获取
- JS脚本非阻塞加载
- JS脚本在前面会导致脚本先被解析,由于线程互斥的,会出现卡顿
- 优化网页性能的技术,它的基本思想是在页面加载过程中,将JS脚本的加载和执行与页面的渲染过程分离开来,从而避免JS脚本的加载和执行阻塞页面的渲染和用户的交互。
- 实现
- 将JS脚本放在页面底部:这样可以让页面的HTML和CSS先加载和渲染完成,再加载JS脚本,从而避免JS脚本的加载和执行阻塞页面的渲染和用户的交互。
- 使用defer属性:在script标签中添加defer属性可以让JS脚本在页面加载完成后再执行,从而避免JS脚本的加载和执行阻塞页面的渲染和
- 缓存策略 会快一些
- SSR
- SSR是指服务器端渲染(Server-Side Rendering),是一种前端优化技术。它的基本思想是将页面的渲染工作从客户端转移到服务器端,将HTML、CSS和JavaScript等资源在服务器端进行处理,然后将最终的HTML页面发送给浏览器。这样可以减轻浏览器的负担,提高页面的加载速度和用户体验。
- 预置 loading 、骨架屏
- 预置loading是指在页面加载过程中,提前在页面上展示一个loading动画或者占位符,以提示用户页面正在加载中,避免用户在等待页面加载时感到无聊或者焦虑。
- 预置loading的作用是提高用户体验,让用户感觉页面加载速度更快,从而减少用户的等待时间和不满意度。同时,预置loading也可以避免页面加载过程中出现空白或者错位的情况,提高页面的美观度和稳定性。
- 骨架屏(Skeleton Screen)是一种优化用户体验的技术,它是在页面加载过程中,先展示一个简单的页面框架,再逐步填充内容的过程。骨架屏通常是一个灰色的占位符,它的形状和页面的布局相似,可以让用户在等待页面加载时,看到一个大致的页面结构,从而减少用户的焦虑和等待时间。
渲染优化
- GPU加速
- 减少回流、重绘 (不要搞table布局)
- 离屏渲染
- 开辟一个内存缓存区渲染好了再合并回来
- 不用的话可能掉帧
- 懒加载
1. GPU加速:GPU加速是一种优化网页性能的技术,它的基本思想是利用计算机的图形处理器(GPU)来加速网页的渲染和动画效果。通过将网页中的一些复杂的图形和动画效果交给GPU来处理,可以减轻CPU的负担,提高网页的渲染速度和流畅度。
2. 减少回流、重绘:回流和重绘是网页性能优化中需要避免的两个操作。回流是指当网页中的元素位置、大小或者内容发生变化时,浏览器需要重新计算元素的位置和大小,然后重新渲染整个页面的过程;重绘是指当网页中的元素样式发生变化时,浏览器需要重新绘制元素的样式,然后重新渲染整个页面的过程。为了避免回流和重绘,可以采取以下几种方式: - 使用CSS3动画代替JavaScript动画,因为CSS3动画可以利用GPU加速,从而提高动画效果的性能。 - 避免频繁操作DOM元素,可以将多个操作合并成一次操作,或者使用文档片段(DocumentFragment)来减少回流和重绘的次数。 - 使用transform和opacity等CSS属性来实现动画效果,因为这些属性不会触发回流和重绘。 - 使用requestAnimationFrame来控制动画的帧率,从而减少回流和重绘的次数。
3. 离屏渲染:离屏渲染是一种优化网页性能的技术,它的基本思想是将一些复杂的图形和动画效果放在一个单独的图层中进行渲染,然后将渲染结果合并到主页面中,从而提高网页的渲染速度和流畅度。离屏渲染通常采用以下几种方式: - 使用CSS3的transform和opacity等属性来创建一个新的图层,从而将图形和动画效果放在一个单独的图层中进行渲染。 - 使用canvas来进行图形渲染,因为canvas可以利用GPU加速,从而提高渲染速度和流畅度。 - 使用WebGL来进行3D渲染,因为WebGL可以利用GPU加速,从而提高渲染速度和流畅度。
4. 懒加载:懒加载是一种优化网页性能的技术,它的基本思想是将网页中的一些不必要的资源(如图片、视频等)延迟加载,直到用户需要访问它们时再进行加载。通过懒加载,可以减少网页的加载时间和带宽占用,提高网页的加载速度和用户体验。懒加载通常采用以下几种方式: - 使用Intersection Observer API来监听元素是否进入视口,从而触发图片的加载。 - 使用jQuery LazyLoad等第三方库来实现图片的懒加载。 - 使用占位符(如loading动画、灰色背景等)来代替图片,从而减少页面的空白时间。 - 对于长页面,可以使用分页或者无限滚动等方式来减少页面的加载时间和带宽占用。
需要注意的是,懒加载也有一些缺点,比如可能会影响SEO、可能会导致用户错过重要的内容等。因此,在实际应用中需要根据具体情况进行权衡和选择。
JS优化
- 防止内存泄漏
- 产生内存泄漏
- 全局变量-使用严格模式+辅助工具检测
- DOM赋值,DOM删除,JS变量没有清空,DOM占空间很大
- 定时器,忘记清除会导致泄露
- 建议使用一个封装的,可以自行销毁的定时器
- 产生内存泄漏
- 循环尽早 break
- 合理使用闭包
- 好的使用可以减少元素创建
- 不好会导致泄露内存
- 减少 Dom 访问
- JS与 渲染引擎的交互很耗时间
- 使用css一次代替
- 使用变量保存DOM,不要每次都查询
- 防抖、节流
- 防抖:防止多次提交只提交最后一次结果
- 节流:多次调用只执行一次
- 拖拽
- 手指移动
- Web Workers
- 新版支持
- 与JS独立,这个执行时,不阻塞渲染
- 文本传输,大的会影响性能
- 可以用于图像 音频处理
04 跨端容器
4.1 跨端容器的由来
- 为什么需要跨端?
- 开发成本
- 开发效率高,成本低
- 一致性体验
- 不像Android IOS 开发生态很多
- 前端开发生态
- 前端开发生态太大,让前端跨端可以解放一部分客户端压力
- 前端开发生态太大,让前端跨端可以解放一部分客户端压力
4.2 常见的跨端方案
- 常见跨端方案列举
- webview
- 小程序
- RN/Weex
- 基本一致
- Lynx
- 字节跨端方案
- Flutter
跨端容器-WebView
- WebView,即网页视图,用于加载网页url,并展示其内容的控件
- 可以内嵌在移动端APP内,实现前端混合开发,大多数混合框架都是基于WebView的二次开发,别入 Ionic,Cordova
- 常见webview分类
- Android IOS 国产Android
- 在APP内打包一个自己的浏览器框架,大致会打上40-50M
- 使用webview优势
- 一次开发,处处使用,学习成本低
- 随时发布,随时更新,不用下载安装包
- 移动设备性能不断提升,性能有保障
- 表现不差
- 通过JSBridge和原生系统进行交互,实现复杂功能
JSBridge是一种前端开发技术,它的基本思想是通过在Web页面中嵌入一个WebView控件,并在WebView中注入一些原生API,从而实现Web页面和原生应用程序之间的通信和交互。 具体来说,JSBridge通常包括以下几个部分: 1. 前端JS代码:前端JS代码负责调用原生API,并将数据传递给原生应用程序。
2. 原生API:原生API是由原生应用程序提供的一些接口或者方法,用于处理前端JS代码传递过来的数据,并将处理结果返回给前端JS代码。
3. WebView控件:WebView控件是一个嵌入在Web页面中的原生控件,它可以加载HTML、CSS、JavaScript等Web资源,并提供与原生应用程序的通信接口。 通过JSBridge技术,前端JS代码可以调用原生API,从而实现一些原生应用程序的功能,如获取设备信息、调用相机、分享等。同时,原生应用程序也可以通过WebView控件向前端JS代码发送消息,从而实现原生应用程序和Web页面之间的双向通信和交互。
需要注意的是,JSBridge技术需要考虑到安全性和性能问题。在安全方面,需要防止恶意代码注入、防止隐私泄露等;在性能方面,需要避免频繁的通信和数据传输,从而提高页面的加载速度和响应速度。因此,在实际应用中需要根据具体情况进行权衡和选择,选择合适的API和注入方式,从而实现页面的功能扩展和交互性提升。
- webview使用原生能力
- JS调用 Native
- API注入: Native 获得 JS 环境上下文,对其挂在的对象或者方法进行拦截
- 使用webview URL Scheme 跳转拦截
- IOS上 window.webkit.messageHandler 直接通信
- Native调用JS
- 直接通过webview暴露的API执行JS代码
- IOS webview.stringByEvaluatingJavaScriptFromString
- Android webview.evaluate.javascript
- 总的来说,本质上都是在web页面中嵌入一个webview控件,后台原生Native向webview中注入(提供)一些API接口,JS代码通过对API的调用,实现原生的一些功能.Native 也可以使用webview中暴露出来的API执行js代码
- JS调用 Native
以下是详细的名词解释。
在移动端开发中,native通常指的是原生应用程序,即使用原生编程语言(如Java、Objective-C、Swift等)编写的应用程序。与之相对应的是Web应用程序,即使用Web技术(如HTML、CSS、JavaScript等)编写的应用程序。
在JS调用native的场景中,native通常指的是原生应用程序提供的一些接口或者方法,通过这些接口或者方法,JS可以调用原生应用程序的功能,如获取设备信息、调用相机、分享等。这些接口或者方法通常是由原生应用程序通过桥接技术(如WebView、JavaScriptCore等)暴露给JS的,从而实现JS调用native的功能。 需要注意的是,JS调用native需要遵循一些安全规范,如防止恶意代码注入、防止隐私泄露等。同时,JS调用native也需要考虑到跨平台兼容性的问题,不同平台的原生应用程序可能提供的接口或者方法不同,需要进行适配和兼容处理。因此,在实际应用中需要根据具体情况进行权衡和选择,选择合适的桥接技术和接口设计,从而实现JS调用native的功能。
API注入是一种前端开发技术,它的基本思想是通过在页面中注入一些API(应用程序接口)来扩展页面的功能和交互性。这些API通常是由第三方库或者插件提供的,如jQuery、React、Vue等。 API注入的优点是可以快速地扩展页面的功能和交互性,从而提高用户体验和开发效率。同时,API注入也可以避免重复编写代码,减少代码量和维护成本。 需要注意的是,API注入也有一些缺点,如可能会影响页面的性能、可能会引入安全漏洞等。因此,在使用API注入技术时,需要注意安全性和性能问题,选择合适的API和注入方式,从而实现页面的功能扩展和交互性提升。
WebView URL Scheme是一种前端开发技术,它的基本思想是通过在WebView控件中加载特定的URL Scheme,从而实现Web页面和原生应用程序之间的通信和交互。
具体来说,WebView URL Scheme通常包括以下几个部分:
1. URL Scheme:URL Scheme是一种特殊的URL协议,它可以唤起原生应用程序,并传递一些参数和数据。
2. 前端JS代码:前端JS代码负责构造URL Scheme,并将数据传递给原生应用程序。
3. 原生应用程序:原生应用程序负责解析URL Scheme,并根据传递的参数和数据执行相应的操作。
通过WebView URL Scheme技术,前端JS代码可以调用原生应用程序,从而实现一些原生应用程序的功能,如打开地图、调用电话、发送短信等。同时,原生应用程序也可以通过URL Scheme向前端JS代码发送消息,从而实现原生应用程序和Web页面之间的双向通信和交互。 需要注意的是,WebView URL Scheme技术需要考虑到安全性和性能问题。在安全方面,需要防止恶意代码注入、防止隐私泄露等;在性能方面,需要避免频繁的通信和数据传输,从而提高页面的加载速度和响应速度。同时,不同的平台和浏览器可能对URL Scheme的支持和限制不同,需要进行适配和兼容处理。因此,在实际应用中需要根据具体情况进行权衡和选择,选择合适的URL Scheme和通信方式,从而实现页面的功能扩展和交互性提升。
- webview<->native 通信
- JS环境中提供通信的JSbridge
- native端提供SDK响应JSB发出的调用
- 前端与客户端分别实现对应的功能模块
- 一个简易的webview实现
- 需要注意
- callid与responseid应该一直
- 回调之后应该删除该次的回调函数,防止越来越大
跨端容器-小程序
- 微信、支付宝、百度小程序、小米直达号
- 渲染层-webview
- 通过内置的魔改的浏览器内核实现多webview架构
- 双线程、多webview架构
- 将渲染与数据(逻辑)分开了
- 将小程序的页面分为多个Webview,每个Webview独立运行,互不干扰,从而提高小程序的性能和稳定性。
- 数据通信、Native转发
- webview、逻辑层、服务端进行通都需要通过native层进行转发或者拦截
- webview、逻辑层、服务端进行通都需要通过native层进行转发或者拦截
跨端容器-React Native/Weex
- 原生组件渲染
- React/Vue框架
- virtual dom
- 是一个JS的对象,对 dom的一个描述
- JSBridge
- 让业务方与Native直接通信
- 渲染流程
- 内部组件通过 React/Vue生成一个 virtual Dom
- 通过JSB提供给Native API
- Native API 通过调用上述内容生成对外的渲染
- 所以在IOS与android上渲染效果不一样,因为主题不同
跨端容器-Lynx
- 主要构成
- 基于 Vue
- 通过 JS Core / V8 绑定Renderer
- JSBridge
- 像上面2,性能更好,相当于优化的JSBridge
- Native UI / 未来可能使用Skia
- 相比于React Native/Weex好处
- 前面是将dom的渲染交给JS来执行,需要大量的创建和运算,首屏渲染就会比较慢
- Lynx首屏渲染很快,因为交给了Native层做渲染。JS只负责业务逻辑上的一些内容
- 渲染流程
跨端容器-Flutter
- 主要组成
- wideget
- Flutter 中基础的UI组件,当前状态下视图的样子
- dart vm
- Dart 语言运行的虚拟环境
- skia 图形库
- 2D图形库
- Flutter 渲染是通过这个图形库画出来的,在各端表现一致
- wideget
- 渲染流程
跨端容器-通用原理
- 不难发现,上述跨端容器在实现原理上有一定的共同性
- UI组件
- 渲染引擎
- 逻辑控制引擎
- 通信桥梁
- 底层API抹平表现差异
跨端方案对比
- 上述开发体验说的是 开发者的开发体验
- webview与小程序由于不涉及到原生等的概念,上手成本很低
- Flutter 学习成本主要在 dart,应该是geogle搞得
跨端容器-思考?
- 同样是基于webview渲染,为什么小程序体验要比webview流畅很多?
- 最大一点,在于小程序做了离线缓存,首屏loading时下载资源,访问的很多接口都是本地的
- 做了定制化,将一些比较危险的操作封死了
- 未来的跨端方案会是什么?
- 这位讲师认为还是webview,主要原因在于flutter代价比较高
课程总结
- 上述内容标星为重要
- 以下为自己对本章内容的一个概括性总结
- 现代浏览器架构使用多进程架构,面向服务架构将会是未来,从单进程到多进程到面向服务架构,一直在改进的主要都是扩展性
- 多进程架构使用多个进程将浏览器加载一个页面时的各个任务分开,比如 网络 CPU 渲染等等,其中渲染是比较重要的环节
- 渲染进程采用多线程架构,将任务进一步细化到了,JS引擎,UI组件,网络线程、事件触发、定时器五大部分,[[05-客户端容器 web浏览器以及跨端方案#2.1 渲染进程多线程架构]],各部分的协同主要在于各部分的功能
- 一个渲染的流程:加载资源、构建渲染树(解析资源)、计算布局、绘制图层
- 浏览器性能优化 [[05-客户端容器 web浏览器以及跨端方案#3.3 浏览器性能优化]] 主要包含了:首屏优化、渲染优化、JS优化 三大模块,分别提供了一定的优化建议与方向。
- 最后在 跨端容器部分,主要介绍 webview,flutter,需要了解webview的原理与渲染机制,以及flutter在远离层面上似乎是完全脱开了webview