虚拟滚动

当我们开发的时候,遇到大数据加载,页面卡顿的问题应该如何处理?大多数情况下,我们都是尽量通过分页的方式处理这类问题,但是总有一些特殊的情况我们必须把数据全部加载到前端进行处理。我曾经遇到过一个坑爹的组件,必须一次性加载几万条数据到页面上,导致一打开这个组件,就会让页面疯狂卡顿,当时便用了虚拟滚动解决了这个棘手的问题。

所以当我们遇到这种情况:渲染长列表的场景,当渲染条数过多时,所需要的渲染时间会很长,滚动时还会造成页面卡顿,整体体验非常不好。完全可以考虑使用虚拟滚动这个解决方案。

虚拟滚动——指的是只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的。以下这个图较为清晰地解释了虚拟滚动的工作原理。

虚拟滚动基本原理:

计算出 totalHeight 列表总高度,并在触发时滚动事件时根据 scrollTop 值不断更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素。

虚拟滚动插件

虚拟滚动的使用场景不少,网上有很多已经造好的轮子,我们也可以适当地选择更快更便捷的方式解决我们的页面问题。

虚拟滚动的插件有很多,比如 vue-virtual-scroller、vue-virtual-scroll-list、react-tiny-virtual-list、react-virtualized 等

这里简单介绍 vue-virtual-scroller 的使用

// 安装插件
npm install vue-virtual-scroller
// main.js
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.use(VueVirtualScroller)
// 使用
<template> 
  <RecycleScroller 
    class="scroller" 
    :items="list" 
    :item-size="32" 
    key-field="id" 
    v-slot="{ item }"> 
      <div class="user"> {{ item.name }} </div>
  </RecycleScroller> 
</template>

该插件主要有 RecycleScroller.vue、DynamicScroller.vue 这两个组件,其中 RecycleScroller 需要 item 的高度为静态的,也就是列表每个 item 的高度都是一致的,而 DynamicScroller 可以兼容 item 的高度为动态的情况

Web Worker优化长任务

浏览器对页面的渲染时单线程的,导致我们的页面无法同时渲染CSS和JS代码。所以由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况。

如果直接把下面这段代码直接丢到主线程中,计算过程中页面一直处于卡死状态,无法操作。

let sum = 0;
for (let i = 0; i < 200000; i++) {
    for (let i = 0; i < 10000; i++) {
      sum += Math.random()
    }
  }

使用 Web Worker 执行上述代码时,计算过程中页面正常可操作、无卡顿

// worker.js
onmessage = function (e) {
  // onmessage获取传入的初始值
  let sum = e.data;
  for (let i = 0; i < 200000; i++) {
    for (let i = 0; i < 10000; i++) {
      sum += Math.random()
    }
  }
  // 将计算的结果传递出去
  postMessage(sum);
}

Web Worker的通信时长

当然Web Worker我们也不能滥用,毕竟加载worker也是需要消耗一定的服务器资源。并不是执行时间超过 50ms 的任务,就可以使用 Web Worker,还要先考虑通信时长的问题假如一个运算执行时长为 100ms,但是通信时长为 300ms, 用了 Web Worker可能会更慢

比如新建一个 web worker, 浏览器会加载对应的 worker.js 资源,下图中的 Time 是这个资源的通信时长(也叫加载时长)。

当任务的运算时长 - 通信时长 > 50ms,推荐使用Web Worker

requestAnimationFrame 制作动画

页面如果想要更加吸人眼球,动画必不可少。但是动画有时候会让页面看起来卡顿,而且也比较消耗浏览器的资源。那么为了解决这样的一些问题,我们可以使用requestAnimationFrame这个API来处理我们的动画效果。

requestAnimationFrame 是浏览器专门为动画提供的 API,它的刷新频率与显示器的频率保持一致,使用该 api 可以解决用 setTimeout/setInterval 制作动画卡顿的情况。

①引擎层面

setTimeout/setInterval 属于 JS引擎,requestAnimationFrame 属于 GUI引擎JS引擎与GUI引擎是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算。

②时间是否准确

requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况。

③性能层面

当页面被隐藏或最小化时,setTimeout/setInterval 定时器仍会在后台执行动画任务,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停。

JS的加载方式

关于我们页面的加载,相信很多人都知道在单线程的加载方式下,很多时候错误的编码会导致页面的阻塞,从而影响用户的体验,那么关于这点,我们应该更详细地去了解JS的加载方式。

①正常模式

<script src="https://www.atool.online/article/index.js"></script>

这种情况下 JS 会阻塞 dom 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其它事情。也就是说如果整个页面跑下来,遇到这行代码将会直接停止页面dom的渲染,等到js文件加载完毕后,才能够再去绘制页面。

②async 模式

<script async src="https://www.atool.online/article/index.js"></script>

async 模式下,它的加载是异步的,JS 不会阻塞 DOM 的渲染,async 加载是无顺序的,当它加载结束,JS 会立即执行。

使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计。

③defer 模式

<script defer src="https://www.atool.online/article/index.js"></script>

defer 模式下,JS 的加载也是异步的,defer 资源会在 DOMContentLoaded 执行之前,并且 defer 是有顺序的加载。

如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回。所以 defer 可以用来控制 JS 文件的执行顺序,比如 element-ui.js 和 vue.js,因为 element-ui.js 依赖于 vue,所以必须先引入 vue.js,再引入 element-ui.js。

defer 使用场景:一般情况下都可以使用 defer,特别是需要控制资源加载顺序时

④module 模式

<script type="module">import {<!--{cke_protected}{C}%3C!%2D%2D%2D%2D%3E--> a } from './a.js'</script>

在主流的现代浏览器中,script 标签的属性可以加上 type=“module”,浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析。

Vite 就是利用浏览器支持原生的 es module 模块,开发时跳过打包的过程,提升编译效率。

⑤preload 模式

<link rel="preload" as="script" href="https://www.atool.online/article/index.js" rel="external nofollow"  rel="external nofollow" >

link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载。

1)preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;

2)preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用;

⑥prefetch 模式

<link rel="prefetch" as="script" href="https://www.atool.online/article/index.js" rel="external nofollow"  rel="external nofollow" >

prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度。

prefetch 特点:

1)pretch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)

2)当页面跳转时,未完成的 prefetch 请求不会被中断

加载方式的总结

async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载。

如今现代框架已经将 preload、prefetch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性去处理资源,这样可以显著提升性能。 所以通常情况下,当我们使用了打包工具时,也不需要我们去管理太多复杂的加载形式,只需要通过简单的配置即可达到我们想要的效果。

到此这篇关于JavaScript前端优化策略深入详解的文章就介绍到这了,更多相关JS前端优化策略内容请搜索阿兔在线工具以前的文章或继续浏览下面的相关文章希望大家以后多多支持阿兔在线工具!

点赞(0)

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部