• js节流和防抖函数


    函数防抖是指对于在事件被触发n秒后再执行的回调,如果在这n秒内又重新被触发,则重新开始计时,是常见的优化,适用于

    • 表单组件输入内容验证
    • 防止多次点击导致表单多次提交

    等情况,防止函数过于频繁的不必要的调用。

    思路

    用 setTimeout 实现计时,配合 clearTimeout 实现“重新开始计时”。

    即只要触发,就会清除上一个计时器,又注册新的一个计时器。直到停止触发 wait 时间后,才会执行回调函数。

    不断触发事件,就会不断重复这个过程,达到防止目标函数过于频繁的调用的目的。

    初步实现

    function debounce(func, wait) {
        let timeout
        return function () {
            clearTimeout(timeout)
            timeout = setTimeout(func, wait) //返回计时器 ID
        }
    }

    示例

    container.onmousemove = debounce(doSomething, 1000);

    注解:关于闭包

    每当事件被触发,执行的都是那个被返回的闭包函数。

    因为闭包带来的其作用域链中引用的上层函数变量声明周期延长的效果,debounce 函数的 settimeout计时器 ID timeout 变量可以在debounce 函数执行结束后依然留存在内存中,供闭包使用。

    优化:修复

    相比于未防抖时的

    container.onmousemove = doSomething 

    防抖优化后,指向 HTMLDivElement 的从 doSomething 函数的 this 变成了闭包匿名函数的 this ,前者变成了指向全局变量。 
    同理,doSomething 函数参数也接收不到 MouseEvent 事件了。

    修复代码

    function debounce(func, wait) {
        let timeout
        return function () {
            let context = this //传给目标函数
            clearTimeout(timeout)
            timeout = setTimeout(
                ()=>{func.apply(context, arguments)} //修复
            , wait)
        }
    }

    优化:立即执行

    相比于 一个周期内最后一次触发后,等待一定时间再执行目标函数;

    我们有时候希望能实现 在一个周期内第一次触发,就立即执行一次,然后一定时间段内都不能再执行目标函数。

    这样,在限制函数频繁执行的同时,可以减少用户等待反馈的时间,提升用户体验。

    代码

    在原来基础上,添加一个是否立即执行的功能

    function debounce(func, wait, immediate) {
        let time
        let debounced = function() {
            let context = this
            if(time) clearTimeout(time)
    
            if(immediate) {
                let callNow = !time
                if(callNow) func.apply(context, arguments)
                time = setTimeout(
                    ()=>{time = null} //见注解
                , wait)
            } else {
                time = setTimeout(
                    ()=>{func.apply(context, arguments)}
                , wait) 
            }
        }
        return debounced
    }

    注解

    把保存计时器 ID 的 time 值设置为 null 有两个作用:

    • 作为开关变量,表明一个周期结束。使得 callNow 为 true,目标函数可以在新的周期里被触发时被执行
    • timeout 作为闭包引用的上层函数的变量,是不会自动回收的。手动将其设置为 null ,让它脱离执行环境,一边垃圾收集器下次运行是将其回收。

    优化:取消立即执行

    添加一个取消立即执行的功能。

    函数也是对象,也可以为其添加属性。

    为了添加 “取消立即执行”功能,为 debounced 函数添加了个 cancel 属性,属性值是一个函数

    debounced.cancel = function() {
            clearTimeout(time)
            time = null
     }

    示例

    var setSomething = debounce(doSomething, 1000, true)
    
    container.onmousemove = setSomething
    
    document.getElementById("button").addEventListener('click', function(){
        setSomething.cancel()
    })

    完整代码

    function debounce(func, wait, immediate) {
        let time
        let debounced = function() {
            let context = this
            if(time) clearTimeout(time)
    
            if(immediate) {
                let callNow = !time
                if(callNow) func.apply(context, arguments)
                time = setTimeout(
                    ()=>{time = null} //见注解
                , wait)
            } else {
                time = setTimeout(
                    ()=>{func.apply(context, arguments)}
                , wait) 
            }
        }
    
        debounced.cancel = function() {
            clearTimeout(time)
            time = null
        }
    
        return debounced
    }

    Vue.js中的解决方案

    首先在公共函数文件中注册debounce

    export function debounce(func, delay) {
      let timer
    
      return function (...args) {
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(() => {
          func.apply(this, args)
        }, delay)
      }
    }

    然后在需要使用的组件中引入debounce,并且在created生命周期内调用:

    created() {
      this.$watch('searchText', debounce((newSearchText) => {
        this.getDatas(newSearchText)
      }, 200))
    }

    节流:

    一个函数执行一次后,只有大于设定的执行周期,才会执行第二次

    //节流函数
        function throttle(fn, delay) {
          //记录上一次函数触发的时间
          var lastTime = 0;
          console.log(this)
          return function () {
            //记录当前函数触发的时间
            var nowTime = Date.now();
            if (nowTime - lastTime > delay) {
              //修正this的指向
              fn.call(this);
              console.log(this);
              lastTime = nowTime;
            }
          }
        }
        document.onscroll = throttle(function () {
          console.log('触发了scroll' + Date.now())
        }, 200)

    防抖:

    有个需要频繁触发函数,出于优化性能角度,在规定的时间内,只让函数触发的第一次生效,后面的不生效。

    function debounce(fn,delay) {
          //记录上一次的延时器
          var timer = null;
          return function () {
            clearTimeout(timer);
            timer = setTimeout(() => {
              fn.apply(this);
            }, delay);
          }
        }
  • 相关阅读:
    【转】Maven多模块项目构建
    【转】vue和springboot项目使用nginx配置,并配置跨域
    关于URL与URI
    C#将16位二进制转换为有符号数
    WPF中 datagird日期列格式
    记录一个困扰了我两个周的Windows网络问题
    基于企业上下级关系的组织机构体系数据模型设计
    WPF简介
    和尚挖井故事给程序员的启示!
    看八个笑话故事 悟八个人生道理
  • 原文地址:https://www.cnblogs.com/gao-xiaomeng/p/9566523.html
Copyright © 2020-2023  润新知