前端性能优化

渲染优化

浏览器重绘(Repaint)和回流(Reflow)

回流必将引起重绘,重绘不一定会引起回流。

重绘(Repaint)

 

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

回流(Reflow)

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
会导致回流的操作:

* 页面首次渲染
* 浏览器窗口大小发生改变
* 元素尺寸或位置发生改变元素内容变化(文字数量或图片大小等等)
* 元素字体大小变化
* 添加或者删除可见的 DOM 元素
* 激活 CSS 伪类(例如:hover)
* 查询某些属性或调用某些方法
* 一些常用且会导致回流的属性和方法
clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()、getComputedStyle()、
getBoundingClientRect()、scrollTo()

性能影响

回流比重绘的代价要更高。
有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

当你访问以下属性或方法时,浏览器会立刻清空队列:

clientWidthclientHeightclientTopclientLeft
offsetWidthoffsetHeightoffsetTopoffsetLeft
scrollWidthscrollHeightscrollTopscrollLeft
widthheight
getComputedStyle()
getBoundingClientRect()

因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

如何避免

CSS

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

Javascript

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
// 优化前 const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 优化后,一次性修改样式,这样可以将三次重排减少到一次重排 const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
  • 也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

 

交互优化

防抖(debounce)/节流(throttle)

防抖(debounce)

输入搜索时,可以用防抖debounce等优化方式,减少http请求;

这里以滚动条事件举例:防抖函数 onscroll 结束时触发一次,延迟执行

function debounce(funcwait) {
  let timeout;
  return function() {
    let context = this; // 指向全局     let args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      func.apply(contextargs); // context.func(args)     }wait);
  };
}
// 使用 window.onscroll = debounce(function() {
  console.log('debounce');
}1000);

节流(throttle)

节流函数:只允许一个函数在N秒内执行一次。滚动条调用接口时,可以用节流throttle等优化方式,减少http请求;

下面还是一个简单的滚动条事件节流函数:节流函数 onscroll 时,每隔一段时间触发一次,像水滴一样

function throttle(fndelay) {
  let prevTime = Date.now();
  return function() {
    let curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(thisarguments);
      prevTime = curTime;
    }
  };
}
// 使用 var throtteScroll = throttle(function() {
  console.log('throtte');
}1000);
window.onscroll = throtteScroll;

 

VUE 优化建议

提取组件的 CSS 到单独到文件

当使用单文件组件时,组件内的 CSS 会以 <style> 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存。

查阅这个构建工具各自的文档来了解更多:

 

优化无限列表性能

如果你的应用存在非常长或者无限滚动的列表,那么采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。

vue-virtual-scroll-list 和 vue-virtual-scroller 都是解决这类问题的开源项目。你也可以参考 Google 工程师的文章 Complexities of an Infinite Scroller 来尝试自己实现一个虚拟的滚动列表来优化性能,主要使用到的技术是 DOM 回收、墓碑元素和滚动锚定。

Google 工程师绘制的无限列表设计

 

通过组件懒加载优化超长应用内容初始渲染性能

上面提到的无限列表的场景,比较适合列表内元素非常相似的情况,不过有时候,你的 Vue 应用的超长列表内的内容往往不尽相同,例如在一个复杂的应用的主界面中,整个主界面由非常多不同的模块组成,而用户看到的往往只有首屏一两个模块。在初始渲染的时候不可见区域的模块也会执行和渲染,带来一些额外的性能开销。

使用组件懒加载在不可见时只需要渲染一个骨架屏,不需要真正渲染组件

 

你可以对组件直接进行懒加载,对于不可见区域的组件内容,直接不进行加载和初始化,避免初始化渲染运行时的开销。具体可以参考们之前的专栏文章 性能优化之组件懒加载: Vue Lazy Component 介绍,了解如何做到组件粒度的懒加载。

 

代码优化相关

1、在js中尽量减少闭包的使用
  原因:使用闭包后,闭包所在的上下文不会被释放

2、在js中避免嵌套循环和"死循环"(一旦遇到死循环,浏览器就会直接卡掉)

3、在js封装过程中,尽量做到低耦合高内聚。减少页面的冗余代码

4、css中设置定位后,最好使用z-index改变盒子的层级,让盒子不在相同的平面上

5、尽量减少使用递归。避免死递归
  解决:建议使用尾递归

6、在事件绑定中,尽可能使用事件委托,减少循环给DOM元素绑定事件处理函数。

posted @ 2022-04-25 17:47  喜葵  阅读(52)  评论(0编辑  收藏  举报