web前端性能优化

1. 用户体验

  1. 软件是用户通往资源的通道。例如:滴滴对应司机、乘客;淘宝、天猫对应购物;饿了么、美团对应周边生活;微信、QQ对应社交分享。性能直接影响用户获取资源体验。
  2. 前端性能重要性
    Amazon发现每100ms延迟导致1%的销量损失。
    谷歌地图首页文件从100KB减少到70KB,流量在第一周涨了10%,在接下来的三周涨了25%
    腾讯根据长期数据监控发现,页面一秒钟延迟会造成页面访问量下降9.4%,跳出率增加8.3%,转化率下降3.5%
  3. 移动端性能
    持续增长的移动用户和移动业务。
    用户的手指操作较为频繁,需要有更快的反馈。
    用户更缺乏耐心,大于3s加载会导致53%的跳出率(bounce rate)。
    其实,不同网络环境、不同网站页面内容、不同用户群体特征,都会影响一个用户在浏览网页时对于性能的忍耐度。
  4. 提高用户体验
    (1) 前端性能提升
    (2) 增加用户耐心度
    不确定的等待会让用户感觉时间过了很久。交互反馈越快越好,例如:Loading
    转移用户注意力。例如:鲁大师。

2. 性能指标

  1. 页面加载指标
    (1) DomcontentloadedLoadFinish
    Domcontentloaded:当初始的 HTML 文档被完全加载和解析完成之后触发。$.ready()是通过此事件实现的。
    Load:页面所有资源加载结束时间。即:window.onload
    Finish:页面所有http请求结束时间。
    这三个指标只与资源加载和数据请求有关,并不能很好的展示真实用户体验,我很可以很简单的利用后置加载来绕过这个时间。
    (2) TTFB(等待时间)
    发出页面请求到接收到应答数据第一个字节所花费的毫秒数。
    可以相对的提供DNS查询,服务器响应,SSL认证,重定向等花费时间的参考。数据请求大部分时间消耗在TTFB,而不是Content Download
    (3) FCP(First Contentful Paint)
    页面首次有内容的时间。例如:加载动画。
    (4) Speed Index(速度指数)
    显示页面的可见部分的平均时间。
    即:轮询得分 = 轮询间隔(一般为100ms) * (1.0 - 页面完成度/100)
  2. 前端性能监测
    (1) 客户端设备
    不同设备的网页打开速度差别很大,测试前端性能需要考虑客户端设备类型。
window.navigator.userAgent

(2) html文档请求各个时间端耗时

var times = window.performance.timing;
console.log('DNS解析时间: ' + (times.domainLookupEnd - times.domainLookupStart));
console.log('TCP连接时间: ' + (times.connectEnd - times.connectStart));
console.log('请求等待时间: ' + (times.responseStart - times.requestStart));
console.log('文档下载时间: ' + (times.responseEnd - times.responseStart));

(3) 总耗时

var times = window.performance.timing;
var startTime = times.navigationStart || times.fetchStart;
console.log('首字节时间: ' + (times.responseStart - startTime));
console.log('Domcontentloaded时间: ' + (times.domContentLoadedEventEnd - startTime));
console.log('Load时间: '+ (times.loadEventEnd - startTime));

(4) 渲染相关时间
渲染相关时间最能够体现页面打开性能。遗憾的是,渲染相关时间并没有统一的api接口。
Chrome浏览器

performance.getEntriesByType('paint').forEach(item => {
    console.log(item.name + ' : ' + item.startTime)
})
//first-paint : 810.2550000185147
//first-contentful-paint : 810.2550000185147

② 通用方法
需要自定义页面首次有内容的时间和首屏渲染结束的时机。

var times = window.performance.timing;
var startTime = times.navigationStart || times.fetchStart;
var FirstContentfulPaint = new Date().getTime();
var FirstScreenTime = new Date().getTime();

console.log('FCP时间: ' + (FirstContentfulPaint - startTime));
console.log('首屏渲染结束时间: ' + (FirstScreenTime - startTime)); 

(5) 资源加载时间
window.performance.getEntriesByType('resource')

  1. 性能监测数据分析
    性能检测数据不宜使用平均数来统计计算,因为平均数非常容易受极大值和极小值的影响。
    推荐使用中位数来描述页面性能数据的集中趋势,然后分析数值区间。
    例如:定义数值区间,“非常快”(<1s)、“快”(1-3s)、“慢”(3-5s)、“非常慢”(>5s)等区间,来观察用户访问性能快慢的分布情况。
    在中位数值区间的基础上,我们可以使用n秒开率作为性能优化指标。
  2. 交互响应指标
    手势交互响应时间
    帧率FPS
    异步请求完成时间

3. 性能测量工具

测量性能指标 性能优化 重新测量性能指标

3.1 Chrome DevTools

  1. 长按刷新按钮 - 清除缓存刷新
  2. 打开调试面板快捷键
    Command + option + i
  3. 自定义调试面板
    Command + Shift + p
    (1) 查看页面帧数
    Show frames per second(FPS) meter
    (2) 阻塞(禁用)某些请求
    Show Request blocking
    (3) 高亮重绘
    Show Rending - Paint flashing
    (4) 性能持续监测
    Show Performance monitor
    (5) 显示图层
    Show Layers
  4. Network
    image.png

    瀑布图(Waterfall)中蓝色的线表示Domcontentloaded,紫色的线表示LoadDomcontentloaded用了1.2sLoad用了1.2sFinish用了3.25s
    页面一共发了28个请求,资源一共6.4MB。天猫首页所有资源仅有2M

4. 传输加载优化

  1. 输入url到页面展示整个过程
    (1) DNS解析
    (2) 建立TCP请求连接
    (3) 服务器请求处理响应
    (4) 客户端下载、解析、渲染显示页面
  2. DNS解析
    浏览器解析域名,拿到对应IP地址之后,才能和服务器进行通信。
    浏览器缓存 → 系统缓存 → 路由器缓存→ISP DNS缓存 → 顶级DNS服务器/根DNS服务器
    在链接对应的东西出现之前就已经解析完毕,能够减少用户点击链接时的延迟。
    DNS预解析<link rel="dns-prefetch" href="http://www.spreadfirefox.com/">
  3. 资源压缩
    启用Gzip压缩、使用Brotli压缩(只支持https)
    配置nginx启用Gzip
  4. 静态资源分多域名存储
    浏览器请求并发限制(Chrome并发6条)针对的是同一个域名下的资源。
    可以静态资源和服务分离,分多域名存储。
  5. 启用Keep Alive
    当使用普通模式,即非Keep Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议)。
    当使用Keep Alive模式(又称持久连接、连接重用)时,Keep Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep Alive功能避免了建立或者重新建立连接。
    Nginx默认开启keep alivehttp 1.1中默认启用keep alive
    (1) keepalive_timeout 避免重新连接最长时间。
    (2) keepalive_requests 避免重新连接最大请求个数。
  6. HTTP资源缓存
    (1) 强缓存
    Cache-Control: public/private/no-cache/no-store
    public:表明响应可以被任何对象缓存。
    private:响应只能被单个用户缓存,不能作为共享(代理服务器)缓存。
    no-cache:强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。
    no-store:不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。
    Pragma
    Pragma:no-cacheCache-Control: no-cache相同。
    Pragma: no-cache兼容http 1.0
    Cache-Control: no-cachehttp 1.1提供的。
    Expires
    服务器端在响应请求时用来规定资源的失效时间。
    优先级:Pragma > Cache-Control > Expires
    (2) 协商缓存
    ETagIf-None-Match
    使用资源的最后修改时间作为协商缓存的标识。
    Last-ModifiedIf-Modified-Since
    使用资源在服务器端的唯一标识作为协商缓存的表示。
  7. Service Workers
    Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。
    注意:只能在localhost或者https中使用
  8. HTTP/2
    HTTP/2 VS HTTP 1.1https://http2.akamai.com/demo
    (1) 基于二进制传输
    HTTP2.0中所有加强性能的核心是二进制传输。
    HTTP1.x中,我们是通过文本的方式传输数据。
    (2) 请求响应多路复用
    一个TCP连接中存在多个流,即可以同时发送多个请求。
    (3) Header压缩
    有效压缩HTTP标头字段来最小化协议开销
    (4) 服务器推送(Server push)
    HTTP2.0中,服务端可以在客户端某个请求后,主动推送其他资源。
    (5) 支持请求优先级
    注意:适合较高的请求量,只能在https中使用
  9. WebSocket技术
    HTTP请求是单向请求,只能由客户端发起。如果服务器有连续的状态变化,我们只能使用轮询。轮询的效率低,非常浪费资源。使用WebSocket,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。
  10. CDN
    将资源缓存在CDN节点上,后续访问即可直接通过CDN节点将资源返回到客户端,不需要重新回到源站服务器。
  11. 优化资源加载的顺序
    浏览器默认安排资源加载优先级 - chrome浏览器networt中的priority
    (1) preload
    <link rel="preload" href="%PUBLIC_URL%/2.png" as="image" />
    提前加载较晚出现,但对当前页面非常重要的资源。优先级高。
    (2) prefetch
    <link rel="prefetch" href="https://code.jquery.com/jquery-3.5.1.slim.min.js" as="script">
    浏览器空闲的时候,提前加载以后用到的资源。优先级低。
    对于当前页面很有必要的资源使用preload,对于可能在将来的页面中使用的资源使用prefetch
    参考:使用preload、prefetch调整优先级

5. 浏览器渲染优化

  1. 浏览器渲染流程
    DOM + CSSOMRender TreeLayoutsPaintComposite
    (1) 解析HTML文档,生成DOM;解析CSS,生成CSSOM
    (2) 根据生成的DOMCSSOM构建渲染树Render Tree
    (3) 根据渲染树,计算每个节点在屏幕的布局(位置、尺寸)信息
    (4) 将渲染树绘制到屏幕上
    (5) 渲染层合并
    注意:布局/回流(Layouts)是计算每个节点的位置和大小(盒子模型)的过程,绘制/重绘(Paint)是像素化每个节点的过程。
  2. 重绘(Paint)与回流(Layouts)
    重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。
    (1) 批量对DOM进行读写操作
    FastDom
const images = document.getElementsByTagName('img');
const update = (timestamp) => {
    for (let i = 0; i < images.length; i++) {
        fastdom.measure(() => {
            const top = images[i].offsetTop;
            fastdom.mutate(() => {
                images[i].style.width = (
                    (Math.sin(top + timestamp / 1000) + 1) * 500 + 'px'
                )
            })
        })
    }
    if(this.state.animate) {
        window.requestAnimationFrame(update);
    }
};
update();

参考:你真的了解回流和重绘吗?
(2) 复合线程(compositor thread)与图层(layers)
以下样式只影响复合(Composite),不会导致页面重排。
transform: translate/scale/rotate/opacity
可以通过willChange: 'transform'样式将元素提出到一个单独的层中。

let divs = [];
for (let i = 0; i < 300; i++) {
    divs.push(
        <div key={i} style={{width: 150, height: 150, overflow: 'hidden', border: '1px solid black'}}>
            <img style={{
                width: 100,
                margin: 10,
                animation: this.state.animate ? '3s linear 1s infinite running rotate' : ''
            }} src={logo} />
        </div>
    )
}
return divs

资源消耗程度:JavaScript操作DOM > 复合层渲染 > 普通DOM渲染
参考:无线性能优化:Composite
注意: PC中报表块在一个单独的图层中,tab切换切换没有导致整个页面重绘。H5中的tab没有在一个单独的图层中,tab切换导致整个页面重绘。

  1. SSR服务器端渲染
    (1) 优势
    加速首屏加载,更好的SEO
    (2) 基于Next.js实现SSR
    系统环境要求:Node.js 10.13或更高版本。
  2. 预渲染(Pre-rendering)页面
    (1) 预渲染作用
    大型单页应用的性能瓶颈- JS下载、解析、执行
    SSR的主要问题:牺牲TTFB来补救First Paint;实现复杂
    预渲染:打包时提前渲染页面,没有服务端参与。
    (2) ⭐️⭐️⭐️ 使用react-snap
    内联样式,避免样式闪动。
    "inlineCss": true
  3. windowingLazy loading提高列表性能
    窗口化(windowing)的含义是只渲染窗口内元素。即,滚入渲染、滚出卸载。
    Lazy loading的含义是滚入渲染,滚出不卸载。
    由于窗口化(windowing)只渲染可见的行,渲染和滚动性能都会有提升。
    react-window可以实现窗口化(windowing)。
    注释:尝试使用react-window实现表格滚动渲染。
  4. 使用骨架组件减少布局移动(Layout Shift)
    (1) Skeleton/Placeholder的作用
    在内容被完全加载出来之前,先将页面骨架加载出来。
    (2) react-placeholder实现骨架组件

6. 代码优化

  1. 同样大小的资源,JavaScript文件加载后解析编译要比其他资源耗长的多。
    (1) Summary中可以看出是哪个js文件
    (2) Evaluate Script为解析耗时
    (3) Compile Script 为编译耗时
    (4) 解析 → 编译 → 执行
  2. V8优化机制
    脚本流
    字节码缓存
    懒解析
  3. HTML优化
    减少iframe的使用。
    压缩空白符,删除注释。
    避免DOM节点深层次嵌套。
    避免table布局。
    注释:iframe会阻塞父文档的加载。
  4. CSS优化
    降低CSS对渲染的阻塞
    利用GPU进行完成动画
    使用font-display属性
    ⭐️⭐️⭐️ 注意:使用contain: layout属性

7. 资源优化

  1. 图片优化方案
    (1) gif、jpg、png、webp格式合理选择
    (2) 图片压缩
    jpeg图片压缩png图片压缩
    (3) 图片懒加载
    原生图片懒加载:<img loading='lazy' src='XXX'>
    第三方图片懒加载:verlok/lazyload
    (4) 渐进式图片
    (5) 响应式图片
  2. 字体优化
    字体未加载完成时,浏览器隐藏或自动降级字体,导致字体闪烁。
    font-display属性值含义如下:
    image.png
  3. 字体图标
    (1) Iconfont
    优点:多个图标一套字体;矢量图;通过CSS修改颜色大小
    缺点:一个图标只能一个颜色
    (2) svg
    优点:保持了图片能力,支持多色彩;独立的矢量图形;XML语法,有利于SEO
    react中使用:svgr/webpack

8. 构建优化

  1. webpack打包速度优化
    (1) noParse
    打包时忽略较大的库。
    被忽略的库不能有importrequiredefine的引入方式
    (2) Dllplugin
    避免开发环境打包时对不变的库重复构建。
  2. Tree Shaking
    webpack中,modeproduction时,默认会开启Tree Shaking
    Tree Shaking基于ES6的模块化语法(importexport)。
    sideEffects
    ⭐️⭐️⭐️ bable需要修改以下配置,不转化ES6模块化语法,以便使用tree shakingsideEffects
['@babel/preset-env', {modules: false}]

注意:需要在现有项目中验证Tree Shaking有没有生效。

  1. 作用域提升
    通过Scope Hoisting优化Webpack输出
    webpack中,modeproduction时,默认会进行作用域提升。
    ⭐️⭐️⭐️ bable需要配置modules: false才会生效。
    注意:需要在现有项目中验证作用域提升有没有生效。该插件已在生产环境中默认启用。
  2. bable配置优化
    ⭐️⭐️⭐️ useBuiltIn设置usage
    按需加载polyfill
  3. 代码拆分
    单个bundle文件拆成若干小bundle/chunks
    (1) optimization.splitChunks配置
    (2) 动态import()
  4. 资源压缩
    TerserPlugin压缩JavaScript
    mini-css-extract-plugin压缩CSS
    HtmlWebpackPlugin minify压缩HTML
  5. 资源持久化缓存
    每个打包的资源文件有唯一的hash值。
    是有修改之后的文件,hash值才会变化。
    webpack中的hash、chunkhash、contenthash区别
  6. webpack打包监测与分析
    (1) 打包体积分析
    Stats分析与可视化图 - Webpack Chart
    webpack-bundle-analyzer体积分析
    ⭐️⭐️ source-map-explorer代码占比分析
    (2) 打包速度分析
    speed-measure-webpack-plugin 打包速度分析
  7. React按需加载实现
    React router使用Reloadable高级组件实现动态引入

9. MVVM框架

MVVM框架在一个非常不错(没有直接操作DOM快)的开发效率和可维护的情况下,提供了一个还过得去的性能。MVVM框架可以较好的规避传统直接操作DOM的方式容易导致过多的页面重绘和回流。

10. 性能优化问题

  1. 首屏加载优化
    测量指标:First Contentful Paint(FCP)Largest Contentful Paint(LCP)Time to Interactive(TTI)
    (1) 资源体积太大
    资源压缩、传输压缩、代码拆分、Tree ShakingHTTP/2、缓存
    (2) 首页内容太多
    路由/组件/内容Lazy loading、预渲染/SSRInline CSS
    (3) 加载顺序不合适
    prefetchpreload
  2. JavaScript内存管理
    变量创建时自动分配内存,不使用时自动“释放”内存 - GC(垃圾回收)。
    局部变量,函数执行完,没有闭包引用,就会被标记回收。
    全局变量,直至浏览器卸载页面时,才会被释放。
    GC实现机制:引用计数、标记清除
    引用计数:无法解决循环引用的问题
    标记清除:标记变量是否还能再次被访问到
    内存管理:避免全局变量产生、避免反复运行引发大量闭包、避免脱离的DOM元素
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容