登录
原创

前端性能优化

发布于 2020-10-15 阅读 109
  • 性能优化
原创

概述

关于 Web 应用性能优化,有一点是毫无疑问的:「页面加载越久,用户体验就越差」,所以 Web 应用性能优化的关键之处就在于:减少页面初载时,所需加载资源的「数量」和「体积」。
那么当所需加载的资源数量到达多少或资源大小小于多少,我们才可以自信地宣称我们的 Web 应用拥有出色的性能呢?下面给出一个参考值,该参考值考虑到了移动端与国外等多种访问环境:

1.页面初载时,所有未压缩的 JavaScript 脚本大小:<=200KB
2.页面初载时,所有未压缩的 CSS 资源大小:<=100KB
3.HTTP 协议下,请求资源数:<=6 个
4.HTTP/2 协议下,请求资源数:<=20 个
5.90%的代码利用率(也就是说,仅允许 10% 的未使用代码)

构建阶段优化

webpack优化

定位体积大的模块

要想对打包体积进行优化,首先得找到体积大的模块,可以使用webpack插件webpack-bundle-analyzer来查看整个项目的体积结构对比,它是以treemap的形式展现出来,很形象直观,还有一些具体的交互形式。既可以查看项目中用到的所有依赖,也可以直观看到各个模块体积在整个项目中的占比。

摇树 tree shaking

tree-shaking可以理解为通过工具"摇"我们的JS文件,将其中用不到的代码"摇"掉。具体来说,在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

作用域提升 scope hoisting

Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余,因此只有那些被引用了一次的模块才能被合并。由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效

代码分割 code splitting

将代码分割成不同的包,然后可以按需加载或并行加载这些包,它可以用来实现更小的包,并控制资源加载优先级

HTML压缩

使用html-webpack-plugin压缩一些在文本文件中有意义,但是在页面上不显示的字符,包括空格、制表符、换行符等。

CSS压缩

使用extract-text-webpack-plugin插件将css文件分离出来。为了使项目加载时候尽早优先加载css样式,也为了解决js文件体积过大的问题

  • 无效代码压缩
  • css语义合并

JS压缩

使用uglifyjs-webpack-plugin将js压缩,减少打包后的vendor.js、bundle.js 等js的文件大小

  • 删除无效字符(空格,换行等)
  • 剔除代码注释
  • 代码语义的缩减和优化
  • 代码保护

图片相关的优化

图片压缩

在不损失用户肉眼可见图片质量的前提下,可以舍弃一些相对无关紧要的色彩信息来减小图片大小。

雪碧图

将网站上用到的一些图片整合到一张单独的图片中,从而减少网站HTTP请求数量。原理为:设定整张雪碧图可示区域,将想要显示的图标定位到该处(左上角);缺点:整合图片比较大时,一次加载比较慢。

内联图片

将图片内容转为base64格式内嵌到html当中,减少网站的HTTP请求数量,常用于处理小图标和背景图片。网页内联图片写法为:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..." alt="">

缺点:

  • 浏览器不会缓存内联图片资源
  • 兼容性差,只支持ie8以上浏览器
  • 超过1mb的图片,base64编码会使图片大小增加,导致网页整体加载速度变慢

所以需要根据实际场景来使用,比如在开发中小于4k或者8k的图片通过构建工具自动inline到html中。

矢量图svg和iconfont

使用iconfont解决icon问题,可以采用阿里巴巴矢量图库:https://www.iconfont.cn/,优势:矢量图标文件体积小可任意设置尺寸大小,清晰度高,缺点:由于是矢量色彩块填充,图标只支持纯色,不支持渐变色图标

网络传输优化

使用gzip压缩

可以最大程度地减少通过网络传输的字节数。gzip压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右。

使用CDN

网站通常将其所有的服务器都放在同一个地方,当用户增加时,公司就必须在多个地理位置不同的服务器上部署内容。为了缩短http请求的时间,我们应该把大量的静态资源放置的离用户近一点。简单点说CDN服务器相当于顺丰快递分布于全国各地的仓库,主仓库将快递运送到这些分仓库,用户可以就近取货,由此加快了速度。除此之外CDN服务器还有许多高级功能,比如防止DDOS攻击等。

懒加载

JavaScript 异步及延迟加载

JavaScript 是 HTML 解析器阻止的。浏览器必须等待 JavaScript 执行才能完成对 HTML 的解析。但是你可以告诉浏览器等待 JavaScript 执行。

异步加载 JavaScript
通过 async 属性,你可以告诉浏览器异步加载脚本。

<script src="app.js" async></script>

Defer JavaScript
defer 属性告诉浏览器在 HTML 解析器完成文档解析之后再运行脚本,但在事件发生之前,将会触发DOMContentLoaded。

<script src="app.js" defer></script>

图片懒加载

图片进入可视区域之后再请求图片资源的方式称为图片懒加载。适用于图片很多,页面很长的业务场景,比如电商;
懒加载的作用:

  • 减少无效资源的加载:
    比如一个网站有十页图片,用户只查看了第一页的图片,这就没必要将十页图片全都加载出来;
  • 并发加载的资源过多会阻塞js的加载,影响网站正常的使用:
    由于浏览器对某一个host name是有并发度上限的,如果图片资源所在的CDN和静态资源所在的CDN是同一个的话,过多图片的并发加载就会阻塞后续js文件的并发加载。

懒加载实现的原理:
监听onscroll事件,判断可视区域位置:
图片的加载是依赖于src路径的,首先可以为所有懒加载的静态资源添加自定义属性字段,用于存储真实的url。比如是图片的话,可以定义data-src属性存储真实的图片地址,src指向loading的图片或占位符。然后当资源进入视口的时候,才将src属性值替换成data-src中存放的真实url。

<img src="image.jpg" alt="">

改为:

<img data-src="image.jpg" class="lazyload" alt="">

预加载

预加载与懒加载正好是相反的过程:懒加载实际上是延迟加载,将我们所需的静态资源加载时间延后;而预加载是将图片等静态资源在使用之前的提前请求,这样资源在使用到时能从缓存中直接加载,从而提升用户体验;
预加载的作用:

  • 提前请求资源,提升加载速度:使用时只需要读取浏览器缓存中提前请求到的资源即可;
  • 维护页面的依赖关系:比如WebGL页面,会依赖一些3D模型,这些都是页面渲染所必须的资源。如果资源都没有加载完毕就进行页面的渲染,就会造成非常不好的体验。

preconnect

下面的代码告诉浏览器你要建立与另一个域的连接。浏览器将为此连接做准备。使用预连接链接标签可以将加载时间缩短 100–500 ms。那么什么时候应该用它呢?直白的说:当你知道在哪里拿东西但不知道该怎么拿。比如哈希样式文件(styles.2f2k1kd.css)这类的东西。

<link rel="preconnect" href="https://example.com">

dns-prefetch

如果你想告诉浏览器将要建立与非关键域的连接,则可以用 dns-prefetch 进行预连接。这大约能够为你节省 20–120 毫秒。

<link rel="dns-prefetch" href="http://example.com">

prefetch

使用预取,你可以告诉浏览器下载链接标记中所指的整个网站。你可以预取页面或资源。预取在加快网站速度方面非常有用,但是要注意有可能降低网站速度的情况。
低端设备或网速较慢的情况下可能会遇到问题,因为浏览器会一直忙于预取。你可以考虑将预取与自适应加载结合使用,也可以将智能预取与 quicklinkGuess.js 结合使用:

<link rel="prefetch" href="index.html" as="document">
<link rel="prefetch" href="main.js" as="script">
<link rel="prefetch" href="main.css" as="style">
<link rel="prefetch" href="font.woff" as="font">
<link rel="prefetch" href="image.webp" as="image">

prerender

<link rel="prerender" href="https://example.com/content/to/prerender">

使用预渲染时,将会先加载内容,然后在后台渲染。当用户导航到预渲染的内容时,内容会立即显示。

preload

借助预加载功能,浏览器会得到引用的资源很重要的提示,应尽快获取。现代浏览器很擅长于对资源进行优先级排序,所以应该只对关键资源使用预加载。可考虑用预连接和预取代替,或者尝试使用 instant.page

<link rel="preload" as="script" href="critical.js">

缓存

缓存你的资源:HTTP 缓存头

缓存是一种可以快速提高网站速度的方法。它减少了老用户的页面加载时间。如果你有权限访问服务器缓存,则用起来非常简单。你可以使用以下API进行缓存:

  • Cache-Control
  • ETag
  • Last-Modified

用 service worker 来缓存你的资源

service worker 是浏览器在后台运行的脚本。缓存是最常用的功能之一,也是你最应该使用的功能。我认为这不是选择问题。通过 service worker 实施缓存,可以使用户与你的站点之间的交互速度更快,而且即使用户断网也可以访问你的网站。

浏览器渲染优化

一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块:

  • HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树;
  • CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施;
  • Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果;
  • 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型;
  • 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果;

函数防抖和节流

防抖

概念:不断触发一个函数,在规定时间内只让最后一次生效,前面都不生效;
实现:定时器;
应用:搜索时等用户完整输入内容后再发送查询请求;

使用函数防抖可以减少事件触发的次数和频率,在某些情况下可以起到优化的作用。比如:搜索框,对于核心业务非搜索的网站,一般都是等待用户完整输入内容后才发送查询请求,一次来减少服务器的压力。像百度这样的核心业务为搜索的网站,服务器性能足够强大,所以不进行函数防抖处理;

节流

概念:不断触发一个函数后,执行第一次,只有大于设定的执行周期后才会执行第二次,以此控制函数执行频率;
实现:定时器,标识;
应用:在游戏中,可以设定人物攻击动作的最快频率,无论手速多快也无法超越这一频率;

重绘与回流

重绘

重绘是一个元素外观的改变所触发的浏览器行为,比如background-color、outline等属性。这些属性不影响布局,只影响元素的外观,风格,会造成DOM元素的重新渲染,这个过程称为重绘。

回流

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就称为回流(reflow)。
发生回流的条件
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
    注:display:none 会触发回流 Reflow,而visibility:hidden 只会触发重绘 Repaint,因为没有发生位置变化。
    注意:回流一定会触发重绘,而重绘不一定会回流
    根据改变的范围和程度,渲染树中或大或小的部分需要重新计算,有些改变会触发整个页面的重排,比如,滚动条出现的时候或者修改了根节点。

CSS动画优化

使用 transform 代替 left、top,减少使用耗性能样式,现代浏览器在完成以下四种属性的动画时,消耗成本较低:

  • position(位置): transform: translate(npx, npx)
  • scale(比例缩放):transform: scale(n)
  • rotation(旋转) :transform: rotate(ndeg)
  • opacity(透明度):opacity: 0…1

如果可以,尽量只使用上述四种属性去控制动画。
不同样式在消耗性能方面是不同的,改变一些属性的开销比改变其他属性要多,因此更可能使动画卡顿。
例如,与改变元素的文本颜色相比,改变元素的 box-shadow 将需要开销大很多的绘图操作。 改变元素的 width 可能比改变其 transform 要多一些开销。如 box-shadow 属性,从渲染角度来讲十分耗性能,原因就是与其他样式相比,它们的绘制代码执行时间过长。
这就是说,如果一个耗性能严重的样式经常需要重绘,那么你就会遇到性能问题。其次你要知道,没有不变的事情,在今天性能很差的样式,可能明天就被优化,并且浏览器之间也存在差异。

提升用户体验

  • 增加loading状态,简称菊花图,在页面上增加loading状态可以缓解用户等待时的焦虑感
  • 使用骨架屏,骨架屏相比菊花图在感官上会更高级更流畅,用户体验更好

评论区

励志做一条安静的咸鱼,从此走上人生巅峰。

0

0

0