前端性能优化

90 阅读21分钟

一感知优化

1.loading 用户进入程序等待时增加loading提示 2.骨架屏 使用一张占位骨架图(svg / lottie / gif)来代替 loading 效果,当数据加载完成后对替换掉骨架图。

二、HTML优化

  • 1.压缩 HTML

HTML代码压缩,将注释、空格和新行从生产文件中删除。删除所有不必要的空格、注释和中断行将减少HTML的大小, 加快网站的页面加载时间,并显著减少用户的下载时间。

  • 2.删除不必要的注释

注释对用户来说是没有用的,应该从生产环境文件中删除。可能需要保留注释的一种情况是:保留远端代码库(keep >the origin for a library)。 我们可以使用HTML minify插件删除注释。 remove-html-comments - npm

  • 3.删除不必要的属性

像 type="text/javascript" or type="text/css" 这样的属性应该被移除。

  • 4.使用语义化标签使用语义化标签可以提高代码的可读性和可维护性,并有助于搜索引擎优化。例如,使用 标签来定义页面头部,使用 标签来定义导航等。
  • 5.减少iframe数量

尽量少用iframe标签,爬虫是不会读取iframe的内容的。

  • 6.削减DOM数量和层级数量

HTML 中标签元素越多,标签的层级越深,浏览器解析 DOM 并制作到浏览器中所花的时间就越长,所以应尽或许坚持 >DOM 元素简洁和扁平化的层级。

  • 7.减少 HTTP 请求次数

将多个 CSS 和 JavaScript 文件合并为一个文件,可以减少 HTTP 请求次数,从而提高页面加载速度。同时,使用>浏览器缓存可以避免每次请求相同的文件。

  • 8.减少重排重绘

重排: 当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每>个页面至少需要一次回流,就是在页面第一次加载的时候。 哪些操作可以影响重排: 添加/删除元素 display:none 移动元素位置 操作styles offsetLeft, scrollTop, clientWidth 修改浏览器大小,字体大小 重绘: 重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘>发生在元素的可见的外观被改变,但并没有影响到布局的时候。 划重点: 重排必定会引发重绘,但重绘不一定会引发重排。

三、JavaScript优化

  • 1.javascript脚本放到页面底部

脚本的下载和执行,会阻塞其他资源(样式文件或图片)的下载。因此,将<script>标签尽量尽可能放到<body>标签的底部。

  • 2.将javascript和css从外部引入

将样式和脚本代码放到外部文件中,并使用链接或脚本标签来引用,可以提高页面加载速度。减少 DOM 操作:DOM 操 作往往是页面加载速度缓慢的主要原因之一。尽量减少 DOM 操作的次数和复杂度,可以提高页面的响应速度和性能。

  • 3.删除重复的脚本

一个页面中如果有两次使用到同一个JavaScript文件,那这将对页面性能产生很大的影响。主要有以下两点: 增加了不必要的HTTP请求。 JavaScript执行所花费的时间。

  • 4.减少DOM访问

访问DOM元素是有代价的,修改DOM元素则更为昂贵,因为它会导致浏览器重新计算页面的几何变化。如下代码: 访问DOM的次数越多,代码运行速度越慢。因此,在有其他方案可以代替的时候,我们要尽量减少访问DOM的次数。

  • 5.节流与防抖

日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并>成一次,只操作一个精确点,JS把这种方式称为debounce(防抖)和throttle(节流)。 防抖和节流能有效减少浏览器引擎的损耗,防止出现页面堵塞卡顿现象。

  • 6.合理的ajax恳求

关于回来内容相同的恳求,没必要每次都直接从服务端拉取,合理运用 AJAX 缓存能加快 AJAX 呼应速度并减轻服务器压力。

  • 7.长列表虚拟滚动优化

虚拟列表是一种用来优化长列表的技术。它可以保证在列表元素不断增加,或者列表元素很多的情况下,依然拥有很好的滚动、浏览性能。它的核心思想在于:只渲染可见区域附近的列表元素。下图左边就是虚拟列表的效果,可以看到只>有视口内和临近视口的上下区域内的元素会被渲染。

  • 8 .代码结构的优化

  • 1.设置Viewport:HTML的viewport可加快页面的渲染。

  • 2.减少DOM结点:DOM结点太多会影响页面的渲染。

  • 3.尽量使用css3动画:合理使用requestAnimationFrame动画代替setTimeout。

  • 4.优化高频事件:scroll、touchmove等事件尽量使用函数防抖节流等进行限制。

    1. 不滥用WEB字体:WEB字体需要下载、解析、重绘当前页面,尽量减少使用。
    1. 文件命名规则须统一且要有意义,同类型文件归类到相同的文件夹中。
  • 7.删除无效注释。

四、CSS优化

  • 1.尽量少用@import 主要有两个原因:

  • 1.使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。这就导致浏览器无法并行下载所需的样式文件。

  • 2.多个@import会导致下载顺序紊乱。在IE中,@import会引发资源文件的下载顺序被打乱,即排列在@import后面的js文件先于@import下载,并且打乱甚至破坏@import自身的并行下载。

  • 2.避免!important,可以选择其他选择器 因为这破坏了样式表中固有的级联规则,使调试bug变得更加困难。它很容易使用不当,而且容易成倍增加,尤其是在滥用时。您可以轻松地!important得出一个带有要覆盖的规则的元素,这时您通常不得不重构样式,或使用另一个!important规则来加剧问题。 如果你一定要使用,最好定义基本样式尽可能靠近html或body元素,并且要覆盖时,请尽量避免使用特殊性。这样,您就有足够的空间进行更改。

  • 3.不要在ID选择器前面进行嵌套其它选择器 主要有两个原因:

  • 1.ID选择器本来就是唯一的而且人家权值那么大,前面嵌套,完全是浪费性能。 2.除了嵌套,在ID选择器前面也不需要加标签或者其它选择器。比如 div#dom 或者.box#dom。这两种方式完全是多余的,理由就是ID在页面就是唯一的。前面加任何东西都是多余的!

  • 4.CSS文件压缩 这应该是最容易想到的一个方法了,通过压缩CSS文件大小来提高页面加载速度。现在的构建工具,如webpack、gulp/grunt、rollup等也都支持CSS压缩功能。压缩后的文件能够明显减小,可以大大降低了浏览器的加载时间。

  • 5.CSS层级嵌套最好不要超过3层 一般情况下,元素的嵌套层级不能超过3级,过度的嵌套会导致代码变得臃肿,沉余,复杂。导致css文件体积变大,造成性能浪费,影响渲染的速度!而且过于依赖HTML文档结构。这样的css样式,维护起来,极度麻烦,如果以后要修改样式,可能要使用!important覆盖。尽量保持简单,不要使用嵌套过多过于复杂的选择器。

  • 6.删除无用的css 我们应该尽可能地提取公共类,减少重复。对于后者,在不同开发者进行代码维护的过程中,总会产生不再使用的CSS的代码,当然一个人编写时也有可能出现这一问题。

  • 7.慎用*通配符 有时候可能会写下面这种代码来消除一些标签的默认样式或统一浏览器对标签渲染的差异化 开发时尽量避免使用通配符选择器。

  • 8.删除不必要的单位和零 CSS 支持多种单位和数字格式,可以删除尾随和跟随的零,零始终是零,添加维度不会为包含的信息附带任何价值。

  • 9.异步加载非首屏css CSS会阻塞DOM的渲染,所以我们将首屏关键CSS内联后,剩余的非首屏CSS内容可以使用外部CSS,并且异步加载,防止非首屏CSS内容阻塞页面的渲染。

  • 10.将样式表放到页面顶部 我们希望浏览器尽早渲染获取到的任何内容。这对大页面和网速慢的用户很重要。给用户视觉反馈,比如进度条的重要性已经被大量研究和记录。在我们的情况中,HTML 页面就是进度条。当浏览器逐步加载页面头部,导航条,logo 等等,这些都是给等待页面的用户的视觉反馈。这优化了整体用户体验。

  • 11.不使用IE的Filter 在图片加载过程中,这个滤镜会阻塞渲染,卡住浏览器,还会增加内存消耗而且是被应用到每个元素的,而不是每个图片,所以会存在一大堆问题。

  • 12.检测工具推荐 给大家推荐两个检测和优化css的工具:

  • 1.根据实际需要选择色深,压缩。 减小图片的大小可以减少页面加载时间。可以使用图片压缩工具来压缩图片,以减小文件大小。

  • 2.小图片引入雪碧图。 雪碧图(CSS Sprite)又叫CSS精灵图主要用于把一堆小图标整合在一张背景透明的大图上,通过设置对应的位置来显示不同的图片,目的是大幅减轻服务器对图片的请求数量,是前端性能优化的一种方式。

优势:

1、减少网页的HTTP请求,提高页面性能

2、减少图片命名的困扰

劣势:

1、需要计算每个图片的位置 2、后期维护困难

3.图片懒加载

  • 一般来说,我们访问网站页面时,其实很多图片并不在首屏中,如果我们都加载的话,相当于是加载了用户不一定会看到图片, 这显然是一种浪费。解决的核心思路就是懒加载:实现方式就是先不给图片设置路径,当图片出现在浏览器可视区域时才设置真正的图片路径。
  • 4..缩小 favicon.ico 并缓存 有利于 favicon.ico 的重复加载,由于一般一个 Web 运用的 favicon.ico 是很少改动的。
  • 5.img图片的alt属性要写, 合理使用target="_blank" 合理而不频繁使用target="_blank" 是可以在一定程度上位站点带来回旋流量和点击的。同一时候。在细节上使用 target="_blank" ,可以增强站点总体用户体验 。
  • 6.采用svg图片或者字体图标 因为字体图标或者SVG是矢量图,代码编写出来的,放大不会失真,而且渲染速度快。字体图标使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便,还有一个优点是生成的文件特别小。
  • 7.Base64 将图片的内容以Base64格式内嵌到HTML中,可以减少HTTP请求数量。但是,由于Base64编码用8位字符表示信息中的6个位,所以编码后大小大约比原始值扩大了 33%。

六、webpack构建优化

    1. 线程加载器 多线程可以提高程序的效率,我们也可以在 Webpack 中使用。而thread-loader是一个可以在Webpack中启用多线程的加载器。 注意: 不一定使用该Loader就一定能提升效率,因为进程之间的通信也会消耗时间,一般在处理文件比较耗时的Loader后面使用该Loader。
    1. 缓存加载器 在我们的项目开发过程中,Webpack 需要多次构建项目。为了加快后续构建,我们可以使用缓存,与缓存相关的加载器是缓存加载器。
    1. Hot update 当我们在项目中修改一个文件时,Webpack 默认会重新构建整个项目,但这并不是必须的。我们只需要重新编译这个文件,效率更高,这种策略称为Hot update。Webpack 内置了Hot update插件,我们只需要在配置中开启Hot update即可。
    1. exclude & include 一些文件和文件夹永远不需要参与构建。所以我们可以在配置文件中指定这些文件,防止Webpack取回它们,从而提高编译效率。
  1. exclude :  不需要编译的文件。
  2. include : 需要编译的文件。
    1. 缩小 CSS 代码 css-minimizer-webpack-plugin 可以压缩和去重 CSS 代码。
    1. 缩小 JavaScript 代码 terser-webpack-plugin 可以压缩和去重 JavaScript 代码。
    1. tree-shaking 作用是把js文件中无用的模块或者代码删掉。而这通常需要借助一些工具。在webpack中tree-shaking就是在打包时移除掉javascript上下文中无用的代码,从而优化打包的结果。在webpack5中已经自带tree-shaking功能,在打包模式为production时,默认开启 tree-shaking功能。
    1. source-map 当我们的代码出现bug时,source-map可以帮助我们快速定位到源代码的位置。但是这个文件很大。因此根据不同的环境来配置。

开发模式: 生成更准确(但更大)的 source-map。

生产方式: 生成更小(但不那么准确)的源映射。

  • 9. Bundle Analyzer 我们可以使用 webpack-bundle-analyzer 来查看打包后的 bundle 文件的体积,然后进行相应的体积优化。
    1. 模块懒加载 如果模块没有延迟加载,整个项目的代码会被打包成一个js文件,导致单个js文件体积非常大。那么当用户请求网页 时,首屏的加载时间会更长。 使用模块来加载后,大js文件会被分割成多个小js文件,加载时网页按需加载,大大提高了首屏的加载速度。
  • 11.压缩包 Gzip是一种常用的文件压缩算法,可以提高传输效率。但是,此功能需要后端配合。
    1. base64 对于一些小图片,可以转成base64编码,这样可以减少用户的HTTP请求次数,提升用户体验。url-loader 在 >webpack5 中已被弃用,我们可以使用 assets-module 代替。 我也顺便也可以添加一些字体图片的配置、音频配置、Eslint配置、babel配置。
    1. 正确配置哈希 我们可以将哈希添加到捆绑文件中,这样可以更轻松地处理缓存。 七、资源加载优化 1.使用 Web Workers Web Worker 是一个独立的线程(独立的执行环境),这就意味着它可以完全和 UI 线程(主线程)并行的执行 js 代码,从而不会阻塞 UI,它和主线程是通过 onmessage 和 postMessage 接口进行通信的。 Web Worker 使得网页中进行多线程编程成为可能。当主线程在处理界面事件时,worker 可以在后台运行,帮你处理大量的数据计算,当计算完成,将计算结果返回给主线程,由主线程更新 DOM 元素。

DNS预解析的实现:

用meta信息来告知浏览器, 当前页面要做DNS预解析: <meta http-equiv="x-dns-prefetch-control" content="on" /> 在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="https://code-nav.top" /> dns-prefetch最大的缺点就是使用它太多。过多的预获取会导致过量的DNS解析,对网络是一种负担。 3.预加载 preload 遇到link标签时,立刻下载并放到内存中,不执行js。 遇到script标签时,将预加载的js执行。 对跨域的文件进行preload时,必须加上 crossorigin 属性 <link rel="preload" crossorigin href="./zone.js" as="script"> 基于标记语言的异步加载: <link rel="preload" as="style" href="asyncstyle.css" onload="this.rel='stylesheet'">

八、浏览器缓存策略

原理:

缓存是浏览器的一种机制,可以把请求过的web资源(html、css、js、图片等)拷贝一份副本存储在浏览器中,并根据请求配置选择是否使用该副本。缓存是浏览器中一种重要的并且简单高效的一种性能优化的方式。一个优秀的缓存策>略起到以下作用:

1.缩短网页请求资源的距离,减少延迟,增加用户体验

2.缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

3.降低服务器压力。

1.缓存位置

Service Worker

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、 如何读取缓存,并且缓存是持续性的。当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取>数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我>们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内 容。

Memory Cache

Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续>性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?

先说结论,这是不可能的。首先计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。内存中其实可以存储大部分的文件,比如说 JSS、HTML、CSS、图片等等。但是浏览器会把哪些文件丢进内存这个过程就很玄学了,我查阅了很多资料都没有一个定论。

当然,我通过一些实践和猜测也得出了一些结论:

对于大文件来说,大概率是不存储在内存中的,反之优先 当前系统内存使用率高的话,文件优先存储进硬盘 Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓 存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一>旦被硬盘缓存下来,就不会再次去请求数据。

Push Cache

Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。

Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及,但是 HTTP/2 将会是日后的一个趋 势。

所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好 可以推送 no-cache 和 no-store 的资源 一旦连接被关闭,Push Cache 就被释放 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存 Push Cache 中的缓存只能被使用一次 浏览器可以拒绝接受已经存在的资源推送 你可以给其他域名推送资源 2.缓存策略 通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。

协商缓存:

如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:Last->Modified 和 ETag 。

当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。 Last-Modified 和 If-Modified-Since

Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。

但是 Last-Modified 存在一些弊端:

如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源 因为以上这些弊端,所以在 HTTP / 1.1 出现了 ETag 。

ETag 和 If-None-Match

ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。

以上就是缓存策略的所有内容了,看到这里,不知道你是否存在这样一个疑问。如果什么缓存策略都没设置,那么浏览器会怎么处理?

对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

强缓存:

强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,>state code 为 200。

Expires Expires 是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。并且 >Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

Cache-control

Cache-control: max-age=30 Cache-Control 出现于 HTTP/1.1,优先级高于 Expires 。该属性值表示资源会在 30 秒后过期,需要再次请求。

Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令

九、服务器优化

1.静态资源使用 CDN

用户与服务器的物理距离对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面, CDN就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。