WEB前端性能优化
请求过程中一些潜在的性能优化点
深入理解http请求的过程是前端性能优化的核心!
- dns是否可以通过缓存减少dns查询时间?
- 网络请求的过程走最近的网络环境?
- 相同的静态资源是否可以缓存?
- 能否减少请求http请求大小?
- 减少http请求数量
- 服务端渲染
DNS解析过程
服务器端请求处理
HTTP状态码
注意:
1、文件合并存在的问题。
- 首屏渲染问题(公共库合并)
- 缓存失效问题(不同页面的合并)
2、资源的合并与压缩(html,css,js压缩和混乱,文件合并,开启gzip)。
- 目的1:减少请求http请求大小
- 目的2:减少http请求数量
图片相关的优化的核心概念
png8/png24/png32之间的区别?
- png8 --- 2^8(256)色 + 支持透明
- png24 --- 2^24色 + 不支持透明
- png32 --- 2^24色 + 支持透明
不同格式图片的特点和常用的业务场景?
- jpg有损压缩,压缩率高,不支持透明(不需要透明图片的的业务场景)
- png支持透明,浏览器兼容性好(需要透明图片的业务场景)
- webp压缩程度更好,在ios webview中有兼容性问题(安卓全部)
- svg矢量图,代码内嵌,相对较小(图片样式相对简单的场景)
对图片的优化有哪些?
- 对图片进行压缩png24->png8(可以使用:https://tinypng.com/)
- CSS雪碧图(可以使用用这个网站生成雪碧图相关的css代码:http://www.spritecow.com)
- Image inline(base64编码)
- 使用矢量图
css、js加载与执行(考虑优化点)
HTMLParser解析器自上而下解析,生成DOM树。
外部资源link、script,浏览器会发起请求。
解析CSS生成CSS规则树,进而构建渲染树(计算element位置,布局layout)。
接着调用操作系统NativeGUI的API进行绘制。
浏览器渲染页面
HTML渲染过程的一些特点
- 顺序执行、并发加载
外部资源并发请求,一个域名下的并发请求数是有上限的。
所以一般将网站的静态资源托管在多个CDN下。
- css阻塞
css在head中可以阻塞页面渲染、css可以阻塞js执行,但是css不阻塞外部脚本的加载。
- js阻塞
直接引入的js阻塞页面的渲染(asyn[异步下载、立即执行]、differ[并行下载、顺序执行]这两种方式加载js例外)
js不阻塞资源的加载(预资源加载器)
js顺序执行,阻塞后续js逻辑的执行(单线程)
懒加载、预加载
懒加载:图片进入可视区域之后请求图片资源。
懒加载的实现:
1.JS判断图片是否进入可视区域。
2.当进入时,修改img
的src属性为实际图片地址。
预加载:图片等静态资源在使用之前提前请求。
预加载实现:
1.使用标签(<img src="xxx" style="display:none">)
2.使用Image
对象(new Image();)
3.使用XMLHTTPRequest
对象(资源跨域问题)
回流与重绘
UI线程和js线程互斥。
css性能能让JavaScript变慢。
频繁触发重绘
与回流
,会导致UI频繁渲染,最终导致js变慢。
回流: 当render tree
中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,
这就称为回流(reflow)。
- 页面渲染初始化
- 盒子模型相关属性变化
- 窗口resize事件触发
- DOM结构变化,比如删除了某个节点
- 获取某些属性,引发回流 很多浏览器会对回流做优化,他会等到足够数量的变化发生,在做一次批处理回流。 但是除了render树的直接变化。
获取以下属性时会引发回流
:
width,height
offsetTop/Left/Width/Height
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
调用了getComputedStyle(), 或者 IE的 currentStyle
重绘: 当render tree
中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会
影响布局的,比如background-color
,这就称为重绘。
Chrome中满足以下任意情况就会创建图层:
video、canvas都是一个独立的图层。
- 3D或透视变换(perspective transform)CSS属性
- 使用加速视频解码的<video>节点
- 拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点
- 混合插件(如Flash)
- 对自己的opacity做CSS动画或使用一个动画webkit变换的元素
- 拥有加速CSS过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
常用的会独立为一个图层的属性:
transform:translateZ(0);
will-change:transform;
怎么优化呢?
- 将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素重绘和回流的影响只会在这个图层中。(但是:浏览器图层合成也会花比较多的时间,所以,是否新建图层得看实际情况,具体问题,具体分析。)
- 用
translate
(改变不会触发浏览器重新布局,但是元素仍会占据原始位置)替代top
(会触发重新布局)改变。 - 用
opacity
(不会触发重绘)替代visibility
(会触发重绘)。 - 不要一条一条地修改DOM的样式,预先定义好
class
,然后修改DOM的className
。 - 把DOM离线后修改。(eg:先把DOM给
display:none
,然后修改100次,再把它显示出来。) - 不要把DOM节点的属性值放在一个循环里当成循环里的变量(offsetHeight、offsetWidth)
- 不要使用
table
布局,一个小的改动会造成整个table
的重新布局。 - 对动画新建图层。
- 启用GPU硬件加速。(transform:translate3d(0,0,0)、transform:translateZ(0))
浏览器存储
Cookie相关
Cookie
因为HTTP请求无状态,所以需要Cookie
去维护客户端状态。
cookie的生成方式:
- 1.http response header中的 set-cookie(服务端生成,客户端保存)
- 2.js中可以通过
document.cookie
可以读写cookie(客户端自身数据的存储)
cookie存储的限制:
- 1.作为浏览器端存储,大小4kb左右
- 2.需要设置过期时间`expire
对于cookie的优化:
cookie中在相关域名下面--cdn的流量损耗
解决方法:__ cdn的域名和主站的域名要分开 __
对于cookie的读写操作:
// 写入 document.cookie = "username=hello"; // 读取 let cookie = document.cookie; /** * param [String] cookie * return [Object] object */ function getCookie(cookie){ if(!cookie){ return null; } let reg = /\s*([^;]+)\s*=\s*([^;]+)\s*/g; let obj = {}; cookie.replace(reg,($0,$1,$2) => { if($1&&$2){ obj[$1] = $2; } }); return obj; }
cookie存储数据能力被localstorage
替代。
httponly :不允许js读写。(防止盗用cookie)
LocalStorage相关
- HTML5设计出来专门用于浏览器存储的
- 大小为5M左右
- 仅在客户端使用,不和服务器进行通信
- 接口封装较好
- 浏览器本地缓存方案
对于LocalStorage的读写操作:
// 写入 localStorage.setItem('username','hello') undefined // 读取 localStorage.getItem('username') "hello"
SessionStorage相关
- 会话级别的浏览器存储
- 大小为5M左右
- 仅在客户端使用,不和服务器进行通信
- 接口封装较好
- 对于表单信息的维护(多页表单数据的维护)
对于 SessionStorage的读写操作:
// 写入 SessionStorage.setItem('username','hello') undefined // 读取 SessionStorage.getItem('username') "hello"
IndexedDB(用的比较少)
- IndexedDB是一种低级API,用于客户端存储大量结构化数据。
- 为应用创建离线版本
PWA相关
- 可靠:在没有网络的环境下也能提供基本的页面访问,而不会出现“未连接到互联网”的页面。
- 快速:针对网页渲染及网络数据访问有较好优化。
- 融入(Engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。
具体内容查看笔记:
ServiceWorker探索
ServiceWorker和cacheStorage缓存及离线开发
利用ServiceWorker进行多页面通信:
// 主页面发送信息 navigator.serviceWorker.controller.postMessage(value); // 主页面监听消息 navigator.serviceWorker.addEventListener('message',event => { // console.log(event.data); }); // ServiceWorker接收信息(对其他页面消息分发) self.addEventListener('message',event => { let promise = self.clients.matchAll().then(clientList => { let senderID = event.source ? event.source : 'unknown'; clientList.forEach(client => { if(client.id === senderID){ return; } client.postMessage({ client: senderID, message: event.data }); }); }); event.waitUntil(promise); });
浏览器缓存
Cache-Control
- max-age 当小于缓存时间时,直接加载本地资源(
from memory cache
),expires
(到期时间http/1.0)和max-age
相比,max-age
具有更高的优先级。 - s-maxage 共享缓存,
public
相关的缓存设备例如CDN,优先级高于max-age
,如果客户端访问到的是CDN服务器缓存中的数据切未更改则返回304状态码。(Cache-control:max-age=3600, s-maxage=31536000
,就算在max-age
时间内,也不直接加载本地文件,而是访问CDN缓存。缓存中的文件如果没有更改,则直接通知客户端304,加载本地文件。感觉和no-cache
很像呀) - no-cache (例如:
Cache-Control:private, max-age=0, no-cache
),不是不缓存的意思,它实际上的机制是,仍然对资源使用缓存,但每一次在使用缓存之前必须(MUST)向服务器对缓存资源进行验证。 - no-store 对该文件不适用任何缓存策略。
- public 资源将被客户端和代理服务器缓存。
- private 资源仅被客户端缓存,代理服务器不缓存。
- public VS private 要知道从服务器到浏览器之间并非只有浏览器能够对资源进行缓存,服务器的返回可能会经过一些中间(intermediate)服务器甚至甚至专业的中间缓存服务器,还有CDN。而有些请求返回是用户级别、是私人的,所以你可能不希望这些中间服务器缓存返回。此时你需要将Cache-Control设置为private以避免暴露。
Expires
Expires是http/1.0
中定义的浏览器缓存策略。(expires: Wed, 24 Jan 2018 12:19:34 GMT
)
- 用来指定资源到期的时间,是服务器端的具体的时间点。
- 告诉浏览器在过期时间前可以直接从缓存取数据,而无需再次请求。
Last-Modified/If-Modified-Since
- 基于客户端和服务端协商的缓存机制
- last-modified —— response header
- if-modified-since —— request header
- 需要与cache-control共同使用
但是last-modified是有缺点的。
- 1.某些服务器
不能
获取精确的修改时间 - 文件修改时间改了,单文件内容却没有变
Etag/If-None-Match
此缓存策略优先级高于Last-Modified/If-Modified-Since
- 文件内容的hash值
- etag —— response header
- if-none-Match —— request header
- 需要与cache-controlgongt使用
总体缓存流程图
服务器端性能
vue渲染遇到的问题
vue执行过程:
下载vue.js ==> 执行vue.js代码 ==> 生成HTML页面
随着前端浏览器的性能的提升,大量的运算在前端执行。
使用vue框架出现了首屏性能、渲染问题。
优化方案?
- 构建层模板编译(在构建层做模板编译工作,将模板语法编译成在
vue
的runtime
中可以直接执行的js代码) - 数据无关的
prerender
的方式(将vue渲染完成的静态页面返回) - 服务端渲染