节流、防抖一套带走

2,071 阅读5分钟

简介

节流和防抖是前端面试中老生常谈的话题,能大大提升程序的性能。但是节流防抖具体是什么?什么情况下使用呢?以及在Vue中应该怎么使用呢?下面请跟随笔者的脚步给大家逐步介绍。

防抖(debounce)

所谓防抖,就是指单位时间内函数只执行一次,如果在单位时间内重复触发该事件,则会重新计算函数执行时间。

常见的使用场景是在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

防抖又分为两种,分为立即执行版本和非立即执行版本。

立即执行版本

立即执行版本我们一般可以用在按钮点击上(比如提交表单),点击立即触发,单位时间内一直点击不会触发。当超过单位时间再次点击会再次触发。

function debounce1(func, delay) {
  let timer = null
  return function() {
    const context = this
    const args = arguments
    if(timer) clearTimeout(timer)
    const canCall = !timer
    timer = setTimeout(() => {
      timer = null
    }, delay)
    
    // if(canCall) func.apply(context, args)
    if(canCall) func.call(context, ...args)
  }
}

非立即执行版本

非立即执行版本我们可以应用在搜索联想功能中,输入内容的时候不触发,输入完后再请求后端获取数据。

function debounce2(func, delay) {
  let timer = null
  return function() {
    const context = this
    const args = arguments
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
      //func.apply(context, args)
      func.call(context, ...args)
    }, delay)
  }
}

组合版本

function debounce3(func, delay, immediate=true) {
  let timer = null
  return function() {
    const context = this
    const args = arguments
    if(timer) clearTimeout(timer)
    if(immediate) {
      const canCall = !timer
      timer = setTimeout(() => {
        timer = null
      }, delay)
      // if(canCall) func.apply(context, args)
      if(canCall) func.call(context, ...args)
    } else {
      timer = setTimeout(() => {
        //func.apply(context, args)
        func.call(context, ...args)
      }, delay)
    }
  }
}

节流(throttle)

所谓节流,就是单位时间内不管触发多少次函数,只固定执行一次函数。  节流会降低函数的执行频率。

常见的使用场景是在一些 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

节流有定时器版本和时间戳版本。

定时器版本

function throttle1(func, delay) {
  let timer = null;
  return function () {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        // func.apply(context, args);
        func.call(context, ...args);
      }, delay);
    }
  };
}

时间戳版本

function throttle2(func, delay) {
  let pre = 0;
  return function () {
    const context = this;
    const args = arguments;
    let now = Date.now();
    if (now - pre > delay) {
      pre = now;
      // func.apply(context, args);
      func.call(context, ...args);
    }
  };
}

组合版本

function throttle3(func, delay, timestamp=true) {
  let timer = null
  let pre = 0
  return function() {
    const context = this
    const args = arguments
    if(timestamp) {
      let now = Date.now()
      if(now - pre > delay) {
        pre = now
        //func.apply(context, args)
        func.call(context, ...args)
      }
    } else {
      if(!timer) {
        timer = setTimeout(() => {
          timer = null;
          // func.apply(context, args);
          func.call(context, ...args);
        }, delay)
      }
    }
  }
}

在Vue中使用

很多小伙伴虽然知道防抖和节流,但是却不知道在Vue中如何使用节流和防抖,下面笔者介绍两种在Vue中使用防抖和节流的方法。

通过外部引入的方式

防抖

import { debounce } from "@/utils/index";
export default {
  mounted() {
    window.addEventListener("scroll", this.handleScroll);
  },
  methods: {
    handleScroll: debounce(function (e) {
      console.log(e);
    }, 1000),
  },
};

节流

import { throttle } from "@/utils/index";
export default {
  mounted() {
    window.addEventListener("scroll", this.handleScroll);
  },
  methods: {
    handleScroll: throttle(function (e) {
      console.log(e);
    }, 1000),
  },
};

写在本文件里面

防抖

  mounted() {
    window.addEventListener("scroll", this.debounce(this.handleScroll, 1000));
  },
  methods: {
    debounce(func, delay) {
      let timer = null;
      return function () {
        const context = this;
        const args = arguments;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(context, args);
        }, delay);
      };
    },
    handleScroll(e) {
      console.log(e);
    },
  },

节流

  mounted() {
    window.addEventListener("scroll", this.throttle(this.handleScroll, 1000));
  },
  methods: {
    throttle(func, delay) {
      let timer = null;
      return function () {
        const context = this;
        const args = arguments;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(context, args);
        }, delay);
      };
    },
    handleScroll(e) {
      console.log(e);
    },
  },

在React中使用

由于React较为灵活,所以使用方式和我们在js中使用是类似的。

通过外部引入的方式

React类组件

import { debounce } from "@/utils/index";

class Test extends Component {
  handleScroll = () => {
    console.log("scroll");
  };
  
  componentDidMount() {
    window.addEventListener("scroll", debounce(this.handleScroll, 1000));
  }
}

如果需要在渲染函数中使用需要绑定this,如果想不绑定this也可以,回调函数需要使用箭头函数。

import { debounce } from "@/utils/index";

class Test extends Component {

  handleClick = debounce(function () {
    console.log("click");
  }, 2000);
  
  // 回调函数需要使用箭头函数,可以不用绑定this
  handleClick2 = debounce(() => {
    console.log("click2");
  }, 2000);
  
  render() {
    return <div>
        <button onClick={this.handleClick.bind(this)}>
          防抖,单位时间内重复触发重复计时
        </button>
        <button onClick={this.handleClick2}>
          防抖,单位时间内重复触发重复计时
        </button>
    </div>
  }
}

React函数组件

函数组件因为没有this,所以我们不需要考虑绑定this问题,所以普通函数和箭头函数作为回调都可以。

import { debounce } from "@/utils/index";

function Test() {

  const handleScroll = () => {
    console.log("scroll");
  };
  
  useEffect(() => {
    window.addEventListener("scroll", debounce(handleScroll, 1000));
  }, []);
}

如果需要在渲染函数中使用也是一样,包裹即可。

import { debounce } from "@/utils/index";

function Test() {

  handleClick = debounce(function () {
    console.log("click");
  }, 2000);
  
  return <button onClick={handleClick}>
          防抖,单位时间内重复触发重复计时
        </button>
}

写在本文件里面

React类组件

class Test extends Component {

  handleScroll = () => {
    console.log("scroll");
  };

  debounce = (func, delay) => {
    let timer = null;
    return function () {
      const context = this;
      const args = arguments;
      if (timer) clearTimeout(timer);
      const canCall = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, delay);

      if (canCall) func.call(context, ...args);
    };
  };
  
  componentDidMount() {
    window.addEventListener("scroll", this.debounce(this.handleScroll, 1000));
  }
}

如果需要在渲染函数中使用需要绑定this,如果想不绑定this也可以,回调函数需要使用箭头函数。

class Test extends Component {

  debounce = (func, delay) => {
    let timer = null;
    return function () {
      const context = this;
      const args = arguments;
      if (timer) clearTimeout(timer);
      const canCall = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, delay);

      if (canCall) func.call(context, ...args);
    };
  };

  handleClick = this.debounce(function () {
    console.log("click");
  }, 2000);
  
  // 回调函数需要使用箭头函数,可以不用绑定this
  handleClick2 = this.debounce(() => {
    console.log("click2");
  }, 2000);
  
  render() {
    return <div>
        <button onClick={this.handleClick.bind(this)}>
          防抖,单位时间内重复触发重复计时
        </button>
        <button onClick={this.handleClick2}>
          防抖,单位时间内重复触发重复计时
        </button>
    </div>
  }
}

React函数组件

函数组件因为没有this,所以我们不需要考虑绑定this问题,所以普通函数和箭头函数作为回调都可以。

function Test() {

  const debounce = (func, delay) => {
    let timer = null;
    return function () {
      const context = this;
      const args = arguments;
      if (timer) clearTimeout(timer);
      const canCall = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, delay);

      if (canCall) func.call(context, ...args);
    };
  };

  const handleScroll = () => {
    console.log("scroll");
  };
  
  useEffect(() => {
    window.addEventListener("scroll", debounce(handleScroll, 1000));
  }, []);
}

如果需要在渲染函数中使用也是一样,包裹即可。

import { debounce } from "@/utils/index";

function Test() {

  const debounce = (func, delay) => {
    let timer = null;
    return function () {
      const context = this;
      const args = arguments;
      if (timer) clearTimeout(timer);
      const canCall = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, delay);

      if (canCall) func.call(context, ...args);
    };
  };

  handleClick = debounce(function () {
    console.log("click");
  }, 2000);
  
  return <button onClick={handleClick}>
          防抖,单位时间内重复触发重复计时
        </button>
}

后记

本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!