阅读 410

浅谈前端的性能-->重绘、回流、防抖、节流

这是我参与新手入门的第2篇文章

浏览器渲染过程

  1. 解析 html ,若遇到 script 标签,立即执行 js,继续向下执行,若 script 标签有 async 关键字,则立刻发起网络请求,若 script 标签有 defer 关键字,则对脚本执行进行延迟,直到页面加载为止。继续向下执行html 标签,构成 htmlDom 树 
  2. 解析 css ,生成 cssom 树 
  3.  将生成的 dom 树与生产的 cssom 树结合,生成渲染树(render tree)

因为 JavaScript 可以同时修改 DOM 和 CSSOM。 由于浏览器不确定特定的 JavaScript 会做什么,所以它采取的预防措施是停止整个 DOM 构造,一般情况我们会将 JavaScript放到页面的底部,等页面要渲染结束再引进来,JavaScript 是影响回流和重绘的关键

回流(reflow)

  • 当render tree中的一部分或全部因为元素的规模尺寸、布局、隐藏等改变时,浏览器重新渲染部分DOM或全部DOM的过程。
  • 浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。
  • 回流也被称为重排,其实从字面上来看,重排更容易让人形象易懂(即重新排版整个页面)

触发回流的条件

  • 调整窗口大小
  • 改变字体大小
  • 增加或者移除样式表
  • 内容变化,比如用户在 input 框中输入文字, CSS3 动画等
  • 更改css display,比如:display:none
  • 操作 class 某些属性
  • 脚本操作 DOM
  • 计算 offsetWidth 和 offsetHeight 属性
  • 设置 style 某些属性的值

重绘

  • 当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如
  • visibility、outline、背景色等属性的改变。
  • 此时由于只需要 UI 层面的重新像素绘制,因此损耗较少。
  • 可以理解:重绘是render tree 的外表的表现,不会改变它的大小,占位。

回流必将引起重绘,而重绘不一定会引起回流。
回流会导致渲染树需要重新计算,开销比重绘大,所以我们要尽量避免回流的产生。

避免回流、重绘

CSS中避免回流、重绘

  1. 尽可能在DOM树的最末端改变class
  2. 避免设置多层内联样式
  3. 动画效果应用到position属性为absolute或fixed的元素上
  4. 避免使用table布局
  5. 使用css3硬件加速,可以让transform、opacity、filters等动画效果不会引起回流重绘

JS操作避免回流、重绘

避免使用JS一个样式修改完接着改下一个样式,最好一次性更改CSS样式,或者将样式列表定义为class的名称 避免频繁操作DOM,使用文档片段创建一个子树,然后再拷贝到文档中 避免循环读取offsetLeft等属性,在循环之前把它们存起来 对于复杂动画效果,使用绝对定位让其脱离文档流,否则会引起父元素及后续元素大量的回流

防抖(debounce)

  • 对于短时间内连续触发的事件,防抖的含义就是让某个时间期限内,事件处理函数只执行一次。
  • 原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

代码分析

// 滚动条加载
// 需求:监听浏览器滚动事件,返回当前滚条与顶部的距离

// 若每次滑动一下,都要计算距顶部的距离,则计算函数的频率使用的就太高了

/*
方法:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,
然后: 如果在200ms内没有再次触发滚动事件,那么就执行函数,
如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时。
*/

function showTop() {
  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}

function debounce(fn, delay) {
  //delay 设置延迟时间
  let timer = null; //借助闭包
  return function () {
    if (timer) {
      // 如果timer 存在,说明当前正在一个计时过程中,并且又触发了相同事件。
      //所以要取消当前的计时
      clearTimeout(timer);
    } // 开始一个新的计时
    timer = setTimeout(fn, delay);
  };
}

debounce(showTop, 200); // 开始执行,会在最后一次滚动结200ms后,执行showTop 函数;
复制代码

节流(throttle)

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限内,事件处理函数只执行一次。
  • 原理是通过判断是否有延迟调用函数未执行。

代码分析

function throttle(fn,delay){
    let notPass= true
    return function() {
       if(!notPass){
           //休息时间 暂不接客 ->刚刚已经执行过,还未到再次执行的时间,直接return false
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
	   notPass = false
        setTimeout(() => {
            fn()
            notPass = true; //执行函数之后,把notPass 置为不可通过的标识
        }, delay)
    }
}

/* 
  请注意,节流函数并不止上面这种实现方案,
  例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
   */

复制代码

防抖与节流的区别

  • 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
  • 函数防抖只是在最后一次事件后才触发一次函数。
  • 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现
  • 节流是执行一次。防抖是执行有限次

寄语

如果你觉得对你有所帮助,记得点赞哦。

文章分类
前端
文章标签