介绍
(ctrl+shift+p) 浏览器刷新频率一般是 60FPS/s
-
用户输入 url 地址 (关键字 会将关键根据默认的引擎生成地址) 会开始导航 浏览器进程里边做
-
浏览器进程会准备一个渲染进程用于渲染页面
-
网络进程加载资源, 最终将加载的资源交给渲染进程来处理
-
渲染完毕显示
-
网络七层模型 (物理层, 数据层) 网络层(ip) 传输层(tcp安全可靠 分段传输, UDP 丢包 一个包传输) (会话层, 表述层, 应用层) http
-
先去查找缓存, 检测缓存是否过期, 直接返回缓存中内容
-
看域名是否解析过, DNS协议 将域名解析成 ip 地址 (DNS 是基于 UDP) ip + 端口号 host
-
请求是 https 需要ssl协商
-
ip地址来进行寻找地址, 排队等待 最多发送6个htt请求(客户端)
-
tcp 创建链接 用于传输(三次握手)
-
利用 tcp 传输数据 (拆分成数据包,有序的) 特点:可靠、有序 服务器会按照顺序来接收(重排)
-
http 请求 (请求行 请求头 请求体)
-
默认不会断开 keep-alive 为了下次传输数据时 可以复用上次的创建的链接
-
服务器收到数据后解析 (响应行 响应头 响应体)
-
服务器 返回301 302 会进行重定向操作 (重新走一遍) 304 会去查询浏览器缓存进行返回
浏览器接收资源后怎么处理
- http 0.9 负责传输 html, 最早的时候没有请求头 和 响应头
- http 1.0 提供了 http 的header 根据header的不同来处理不同的资源
- http 1.1 默认开启了 keep-alive 链接复用 增加管线化(避免等待建立链接) (服务器处理多个请求(队头阻塞问题))
- http 2.0 用同一个 tcp 链接来发送数据 一个域名一个tcp(多路复用) 头部压缩 服务器可以推送数据
- http 3.0 解决了 tcp 的队头阻塞问题 加上QUIC协议并采用了 udpp
渲染流程
- 解析html转化成 DOM 树 -> 解析css 也会解析成 styleSheets -> 然后解析成渲染树 -> 图层树 -> 绘制(paint) -> 合成线层(合成图层 com)
- 样式是不会去阻塞解析的 渲染dom时,要等待样式加载完毕
- 样式放在底部,可能会导致重绘, 当html渲染时,会先扫描 js 和css 渲染从上到下,边解析边渲染
- js 会阻塞dom解析, 需要暂停DOM 解析去执行 js, js可能会操作样式,所以需要等待样式加载完成再继续加载, js阻塞html解析,阻塞渲染,js 要等上面的css加载完毕,保证页面js 可以操作样式
Perfomance 使用
- TTFB
- time to first byte(首字节时间)
- 从请求到数据返回第一个字节所消耗的时间
- TTI
- Time to Interactive(可交互时间)
- DOM 树构建完毕,代表可以绑定事件
- DCL
- DOMContentLoaded(事件耗时)
- 当HTML 文档被完全加载完毕之后才, DOMContentLoaded事件会触发
- L
- onLoad(事件耗时)
- 当依赖的资源全部加载完毕之后才会触发
- FP
- First Paint(首次绘制)
- 第一个像素点绘制到屏幕的时间
- FCP
- Frist Contentful Paint(首次内容绘制)
- 首次绘制任何文本,图像,非空白节点的时间
- FMP
- Frist Meaningful paint(首次有意义绘制)
- 首次有意义绘制是页面可用性的度量标准
- LCP
- Largest Contentful Paint(最大内容渲染)
- 在 viewport 中最大的页面元素加载的时间
- FID
- Frist Input Delay(首次输入延迟)
- 用户首次和页面交互(单击链接, 点击按钮等) 到页面响应交互的时间
浏览器运行机制
1. 浏览器是多进程的区分进程和线程?
- 进程是一个工厂,工厂有它的独立资源
- 工厂之间相互独立
- 线程是工厂中的工人,多个工人协作完成任务
- 工厂内有一个或多个工人
- 工人之间共享空间
- 工厂的资源 -> 系统分配的内存(独立的一块内存)
- 工厂之间的相互独立 -> 进程之间相互独立
- 多个工人协作完成任务 -> 多个线程在进程中协作完成任务
- 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成
- 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)
最后,再用较为官方的术语描述一遍:
- 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
- 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
- 不同进程之间也可以通信,不过代价较大
- 现在,一般通用的叫法:单线程与多线程,都是指在一个进程内的单和多。(所以核心还是得属于一个进程才行)
2. 浏览器都包含哪些进程?
1.Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
负责浏览器界面显示,与用户交互。如前进,后退等
负责各个页面的管理,创建和销毁其他进程
将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
网络资源的管理,下载等2.第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
3.GPU进程:最多一个,用于3D绘制等
4.浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为
页面渲染,脚本执行,事件处理等
3. 浏览器多进程的优势?
- 避免单个page crash影响整个浏览器
- 避免第三方插件crash影响整个浏览器
- 多进程充分利用多核优势
- 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
4. 浏览器内核(渲染进程)
1.GUI渲染线程
- 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
- 当界面需要重绘(Repaint )或由于某种操作金发回流 (reflow )时,该线程就会执行
- 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结),GUI更新会保存在一个队列中等到JS引擎空闲时立即被执行
2.JS引擎线程
- 也称为JS内核,负责处理JavaScript脚本.(例如V8引擎)
- JS引擎线程负责解析JavaScript脚本运行代码
- JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
- 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞
3.事件触发线程
- 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
- 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
- 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的对尾,等待JS引擎的处理
- 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
4.定时触发器线程
- 传说中的 setInterval 与 setTimeout 所在线程
- 浏览器定时计数器并不是由JavaScript 引擎计数的,(因为JavaScript引擎是单线程的,如果处于阻塞线程状态就会影响计时的准确)
- 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
- 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
5.异步http请求线程
- 在XMLHttpRequest在连接后通过浏览器新开一个线程请求
- 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调在放入事件队列中。再由JavaScript引擎执行
5. Browser进程和浏览器内核(Renderer进程)的通信过程
如果自己打开任务管理器,然后打开一个浏览器,就可以看到:任务管理器中出现了两个进程(一个是主控进程,一个则是打开Tab页的渲染进程),然后在这前提下,看下整个过程
- Browser进程收到用户请求,首先需要获取页面内容(比如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程
- Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,然后开始渲染
- 渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程帮助渲染
- 当然可能会有JS线程操作DOM(这样可能会造成回流并重绘)
- 最后Render进程将结果传递给Browser进程
- Browser进程接收到结果并将结果绘制出来
6. 梳理浏览器内核中线程之间的关系
-
GUI渲染线程与JS引擎线程互斥
由于JavaScript是可操作DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲线程前后获得的元素数据就可能不一致。因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染进程与JS渲染引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行
-
JS阻塞页面加载
尽量避免JS执行事件过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉 -
WebWorker,JS的多线程?
JS引擎是单线程的,而且JS执行时间过长会阻塞页面,那么JS就真的对cpu密集型计算无能为力么?
后来HTML5支持了 web workerWeb Worker为web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面
一个worker是使用一个构造函数创建的一个 Object(e.g.Worker())运行一个命名的JavaScript文件,这个文件包含将工作线程中运行的代码;workers运行在另一个全局上下文中,不同当前的window,因此,使用window快捷方式获取当前全局的范围,在一个worker内将返回错误
这样理解
- 创建爱你worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
- JS引擎线程与worder线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
所以,如果有非常耗时的工作,请单独开一个 Worker 线程,这样里面不管如何翻天覆地都不会影响JS引擎主线程,只待计算好出结果后,将结果通信给主线程即可。
而且注意下,JS引擎是单线程的,这一点的本质仍然未改变,worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。
7.WebWorker 与 SharedWorker
- WebWorker 只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享
- 所以Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序,在浏览器中每个相同的JavaScript只存在一个SharedWorker进程,不管它被创建多少次。
本质上就是进程和线程的区别。SharedWorker 由独立的进程管理,WebWorker只是属于render进程下的一个线程
8.浏览器渲染流程
- 浏览器输入url,浏览器主进程接管,开一个下载线程,然后进行 http 请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,随后将内容通过RendererHost接口转交给Renderer进程
- 浏览器渲染流程开始
浏览器内核拿到内容后,渲染大概可以划分成一下几个步骤
- 解析html建立dom树
- 解析css构建render树(将css代码解析成树形的数据结构,然后结合DOM合并成render树)
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。
load事件与DOMContentLoaded事件的先后
上面提到,渲染完毕后会触发load事件,那么你能分清出load事件与DOMContentLoaded事件的先后么?
- 当DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片(比如如果有async加载的脚本就不一定完成)
- 当onload事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了。(渲染完毕了)
- 顺序是:DOMContentLoaded -> load
css加载是否会阻塞dom树渲染?
这里说的是头部引入css的情况
首先,我们都知道:css是由单独的下载线程异步下载的。
这可能也是浏览器的一种优化机制
因为你加载css的时候,可能会修改下面DOM节点的样式
如果css加载不阻塞render树渲染的话,那么当css加载完之后,
render树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。
所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,
在根据最终的样式来渲染render树,这种做法性能方面确实会比较好一点。
普通图层和复合图层
渲染步骤中就提到了 composite 概念。
可以简单的这样理解,浏览器渲染的图层一般包括两大类:普通图层以及复合图层
首先,普通文档流内可以理解为一个复合图层(这里称为默认复合层,里面不管添加多少元素,其实都在同一个复合图层中)
其次,absolute布局(fixed也是一样),虽然可以脱离普通文档流,但它仍然属于默认复合层。
优化策略
网络优化
- 减少HTTP请求数, 合并js、css,合理嵌套 css、js
- 合理设置服务端缓存,提高服务器处理速度.(强缓存、对比缓存)
- Expires/Cache-Control Etag/if-none-match/last-modified/if-modified-since - 避免重定向,重定向会降低响应速度(301, 302)
- 使用 dns-prefetch, 进行DNS 预解析
- 采用域名分片技术, 将资源放到不同的域名下, 接触同一个域名最多处理 6个TCP链接问题
- 采用 CDN 加速加快访问速度,(指派最近、高度可用)
- gzip 压缩优化 对传输资源进行体积压缩 (html、css、js) img,video等不建议打包
- // Content-Encoding: gzip - 加载数据优先级: preload(预先请求当前页面需要的资源) prefetch(将来页面中使用的资源) 将数据缓存到HTTP 缓存中
关键渲染路径
- 重排: 添加元素,修改大小,删除元素,获取位置信息,移动元素位置, 都会触发重排
- 脱离文档流
- 渲染时给 图片 增加固定宽高
- 尽量使用 css3 动画
- 可以使用 will-change 提取到单独的图层中
图片优化
- 图片格式
- jpg: 适合色彩丰富的照片、banner图; 不适合图形文字、图标(纹理边缘有锯齿),不支持透明度
- png: 适合纯色、透明、图标,支持半透明; 不适合色彩丰富图片,因为无损存储会导致存储体积大
- gif: 适合动画,可以动的图标;不支持半透明,不适合存储彩色图片
- webp:适合半透明图片,可以保证图片质量和较小的体积 (兼容性不好)
- svg: 格式图片;相比于 jpg 和 png它的体积更小,渲染成本过高,适合小且色彩单一的图标
- 避免空 src 的图片
- 减少图片尺寸, 节约用户流量
- img 标签设置 alt 属性,提升图片加载失败时的用户体验
- 原生的 loading:lazy 图片懒加载
<img loading='lazy' src='./img/1.jpg' width='300' height='300'>
一般不用这个,因为不知道什么时候图片会加载 - 不同环境下,加载不同尺寸和像素的图片
<img src='./img/1.jpg' sizes='(max-width:500px) 100px,(max-width:600px) 200px' srcset='./img/2.jpg 100w ./img/3.jpg 200w'>
w:宽度 - 对于较大的图片可以考虑采用渐进性图片(设计去做, 一点一点加载) 一般小
- 采用 base64URl 减少图片请求 缺陷非常大(如果图片大,会比以前图片大3分之一)
- 采用雪碧图合并图标图片等
HTML 优化
- 语义化 HTML: 代码简洁清晰,利于搜索引擎,便于团队开发
- 提前声明字符编码,让浏览器快速确定如果渲染网页内容 html lang=“zh-CN”
- 减少 HTML 嵌套关系、减少DOM 节点数量
- 删除多余空格、空行、注释、及无用的属性等
- HTML 减少 iframes 使用(iframe 会阻塞 onload 事件可以动态加载 iframe) 子页面加载完才能加载父页面(异步加载), src 动态加载
- 避免使用 table 布局
CSS 优化
- 减少伪类选择器、减少样式层数、减少使用通配符(可忽略)
- 避免使用 css 表达式, css 表达式会频繁求值,当滚动页面,或者移动鼠标时都会重新计算(IE6,7) (可忽略)
- 删除空行、注释、减少无意义的单位、css进行压缩
- 使用外链 css,可以对 css 进行缓存
- 添加媒体字段,只加载有效的 css 文件
<link href="index.css" rel="stylesheet" media="screen and (min-width:1024px)">
- css contain 属性,将元素进行隔离 (减少渲染资源)
- 减少 @import 使用, 由于 @import 采用的是串行加载 (link 是并行加载)
js 优化
- 通过 async(加载完了就立即执行)、defer(加载完后等待执行) 异步加载文件
- 减少 DOM 操作,缓存访问过的元素 (每次都重绘)
- 操作不直接应用到 DOM 上, 而应用到虚拟 DOM 上. 最后一次性的应用到DOM上
- 使用 webworker 解决程序阻塞问题
- IntersectionObserver ( 滚动API )
- 虚拟滚动 vertual-scroll-list
- requestAnimationFrame、requestIdleCallback (frqme 生命周期)
- 尽量避免使用 eval, 消耗时间久
- 使用事件委托,减少事件绑定个数
- 尽量使用 canvas 动画、css动画
字体优化
FOUT、FOIT
优化策略方法
- 关键资源个数越多,首次页面加载时间就会越长
- 关键资源的大小,内容越小,下载时间越短
- 优化白屏: 内联 css 和内联 js 移除文件下载,较小文件体积
- 预渲染,打包时进行渲染
- 使用 SSR 加速首屏加载,(耗费服务端资源), 有利于 SEO 优化. 首屏利用服务端渲染,后续交互采用客户端渲染
- 静态化
浏览器的存储
- cookie: cookie过期时间内一直有效,存储大小 4k 左右、同时限制字段个数,不适合大量的数据存储,每次请求会携带 cookie, 主要可以利用身份检查
- 设置 cookie 有效期
- 根据不同子域划分 cookie 较少传输
- 静态资源域名和 cookie 域名采用不同域名,避免静态资源访问时携带 cookie
- localStorage: chrome下最大存储 5M, 除非手动清除,否则一直存在,利用 localStorage 存储静态资源
- sessionStorage: 会话级别存储,可用于页面间的传值
- indexDB: 浏览器的本地数据库(基本无上限)
增加体验 PWA
- web App Manifest: 将网站添加到桌面、更类似 native 的体验
- Service Worker: 离线缓存内容, 配合cache API
- Push Api && Notification Api : 消息推送与提醒
- App Shell && App Skeleton: App 壳、骨架屏
LightHouse 使用
- npm install lighthouse -g
- lighthouse --view
http://baidu.com
缓存机制
介绍
浏览器缓存机制有四个方面,按照获取资源时请求的优先级依次排列为:Memory Cache、Service Worker Cache、HTTP Cache、Push Cache。
Memory Cache (内存缓存)
- 缓存存在内存中,在优先级方面,它是浏览器第一个尝试访问的缓存。浏览器秉承的是“节约原则”。Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。
- 内存缓存中有一块重要的缓存资源是preloader相关指令(例如 )下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。
- 还有一个磁盘缓存
Service Worker
-
我们书写的js代码通常是在主线程运行,可以访问DOM与Windows全局变量,Service Worker与Web Worker则是独立于主线程的JavaScript线层,它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。同时Service Worker对协议也是有要求的,必须是https。
-
Service Worker 的生命周期包括 install、active、working 三个阶段。一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。
HTTP Cache
- Cache-Control、expires 等字段控制的缓存 (gzip)
Push Cache (推送缓存)
- Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。