msdlisper Front-end

web性能优化实践

2020-02-05

性能优化, 从和说起

引入

在软件开发行业, 优化是永恒的话题, 特别是前端领域, 性能优化总是和用户体验挂钩. 我们的页面加载的越快, 页面反应越快, 用户体验越好, 当然用户体验包括其他很多点.

我目前主要是在移动端webapp方向积累了一些优化经验

了解浏览器的几个时间节点

我们可以通过Navigation Timing API 来获取各个时间节点, 也就是通过window.performance.timing来获取

  • 起点: 一般使用fetchStart, navigationStart是包括前一个页面卸载用的时间
  • 白屏时间: 一般是domLoading-fetchStart, 头部资源加载完成
  • 首屏时间: dom树已经解析完成, 一般是domContentLoadEventEnd-fetchstart, 也就是常说的domready时间.
  • 所以资源加载完成时间: loadEventEnd-fetchStart, load事件的事件

上面指标,对于SPA应用, 其实意义不是很多, 当然现代浏览器已经有针对SPA的一个统计指标, 这些用chrome的performace工具可以得到分析:

  • FP/FCP 首次绘制, 这个时候: 页面一般会呈现出loading图标
    • 通过服务端渲染, 内联css可以加快
  • DCL/LOAD 首次渲染: 页面一般会出现footer, 或者顶部banner
    • 通过CDN加速, 缓存, 以及GZip来提高
  • FMP 首次有效绘制/可交互状态时间TTI: 这个时候出现了页面架子, 但图片啥的可能没加载完
    • 通过精简业务逻辑, 路由拆包来提高
  • 加载完成: 图片啥的都加载完成了
    • 图片压缩, webp来提速

这个可以参数浏览器术语

优化js的加载速度

如果不做任何优化, js文件的下载和执行都还中断html parser, 如果header里的script文件太大, 白屏的时间就会很长

接下来了解 prefetch, preload, dns-prefetch, defer和async

prefetch

形如 <link href="main.js" rel="prefetch">, 浏览器利用空余时间来加载js, 存储在disk里, 当浏览器某一时刻发现script里用了main.js, 就从disk里面取.

适合对于非首屏用到的js文件, 提前加载好需要用的js资源

使用的谨慎, 对于页面马上要用的资源, 不要用prefetch, 不然js没下载完, 又要用这个js, 浏览器又会再次请求

preload

形如<link rel="preload" href="/main.js" as="script">, 浏览器遇到这个标签就立即加载main.js, 这个过程不阻塞页面parser, 等要用到main.js的时候, 就从内存里读取disk

这个适合首屏的js用, 可以结合webpack的插件:ScriptExtHtmlWebpackPlugin

dns-prefetch

形如: <link rel="dns-prefetch" href="//cdn.com/">. 预先解析cdn的DNS地址, 从而提高下载速度

defer和async

prefetch和preload是解决下载时间问题, js执行也会阻断HTML Parser的, defer/async属性可以解决这问题, 可以结合webpack的插件:ScriptExtHtmlWebpackPlugin

defer是在所有元素解析完成后, DOMContentLoaded事件前执行js; js是依次执行

  • 脚本的下载不会中断parse html
  • 脚本执行完后才开始渲染(render/paint)

async是在脚本下载完成后执行, 执行顺序不固定, 用于加载google analysis之类的库, 谁快谁先执行

  • 脚本的下载不会中断parse html
  • 在render和paint后开始执行和求值, 这个过程在load事件前

拆包

要让js加载的更快的方法, 就是想办法让js包变到最小

一般我们可以使用BundleAnalyzerPlugin来分享webpack打包里大小, 找到比较大的包后, 就可以针对这个包做一些拆解

按需加载

将没有用到的代码, 就不要打进来了

import * as mathjs from 'mathjs’

vs

import {  create,  divideDependencies,  evaluateDependencies,  powDependencies
} from 'mathjs’

或者使用 LodashModuleReplacementPlugin 之类的拆包插件

替换

小包替换大包:

Moment -> day.js (Gzip 65k -> 2k)

Mathjs -> decimal.js (Gzip 108k -> 12k)

路由异步加载

// 借钱 - 申请
{
    path: '/loan',
    component: () => import(/* webpackChunkName: "loan.apply" */ '@/views/loan/apply.vue'),
},
// 借钱 - 绑卡
{
    path: '/loan/bindcard',
    component: () =>
      import(/* webpackChunkName: "loan.bindcard" */ '@/views/loan/apply-bind-card.vue'),
}

yarn.lock文件导致重复打包

分析打包后的结构图, 你会或多或少的发现, 某个包因为版本不一致重复引用的情况:

比方A依赖=>B@0.0.1, C依赖=>B@0.0.3, 结果, 我们的vender.js可能即包括B@0.0.1, 也包括B@0.0.3; 当然, 你确认没问题后, 手动安装一个版本就行

还不满足? 上Performance指标

接下来, 我们可以尝试精简业务逻辑, 看看到底是哪个代码执行这么久, 然后干掉他

我们在开发环境下, 打开控制台, 找到Performance, 具体怎么用, 网上有介绍, 不多说, 看图:

通过call tree, 你可以看到每个代码具体的执行时间(Total Time), 重耗时最长的开始, 一步一步的查下去, 你会发现, 原来有些不必要的代码, 执行了这么久….

利用缓存

这里又有很多话题, 但这这里简单提几点

  • 接CDN, 大大的加快了静态资源的下周速度, 对于https的原则, 还避免了加密造成的性能损耗
  • 离线缓存, 如果你是使用Hybrid技术实现的端内app, 可以尝试他, 能真正让你的应用秒开. 当然, 需要端上支持. 原理就是我们的静态文件不是用户访问才去下载, 而是伴随着发版, app帮我们预先加载到disk, 但需要商量好缓存的更新时机

图片的优化

最后一步, 就是当页面达到可交互状态时, 这个图片不一定都加载完了, 往往很多图片又是高清图片, 你又不能压缩的太厉害, 那这么办呢?

图片渐进加载

这里已经是可以一定程度上提升用户体验了

这里的目标就是不让用户看的图片加载是一卡一卡的, 而是平滑的加载. 并且如果判断浏览器是否支持webp, 支持的话, 使用webp

你可以封装成一个directive或者component, 类似

<img v-webp  src="//xxx.jpg"/>
<div v-webp />

img标签里的图片先是一张缩略图, 这个图片小, 很快就出来了, 与此同时, 加载高清图, 高清图片加载完成后, 再将缩略图替换

// 部分实现
this.bigImg = new Image();
this.bigImg.onload = () => {
  this.isFullLoad = true;
};

div元素如果使用了background: url(…)做背景图, 也可以加渐变效果

至于实现, 再说

图片懒加载

看情况, 如果图片特别长,特别多, 可以尝试. 原理就是监控图片是否在可视区域, 是的话才去加载

引用


上一篇 Hybrid的认识

Comments

Content