防抖和节流

节流和防抖都是为了防止函数调用太快或者太多次。

可视化比较防抖和节流

其中_.debounce,_.throttle, raf-schd都提供了cancel()方法,用于取消延时回调

1.防抖(debounce)

防抖: 一段时间不活动之后发布更改。

原理: 设置一个定时器和最小间隔时间,如果用户触发在时间间隔内,就清空定时器,重新计时;

          用户停止触发,且在最小时间间隔内没有再次触发,则发布更改。

分类: 防抖函数有两种,一种是第一次触发执行回调函数;一种是第一次触发不执行回调函数。

应用: 在某个动作(鼠标滚动,键盘输入)等结束后调用回调函数。

示例1:(_.debounce)

默认配置

 {leading: false, trailing: true}
// 第一次默认不触发;最后一次默认触发

代码如下:

class App extends React.Component {
  constructor(props){
    super(props);
    this.handleChangeDebounce = debounce(this.handleChange, 250)
  }
  componentWillUnmount() {
    this.handleChangeDebounce.cancel();
  }
  handleChange = (points) => {
    console.log('done',points);
  }
  render() {
    const style ={width:300, height:300,border:'1px solid red'};
    return (
      <div 
        style={style} 
        // 如果想要访问事件的属性,只能通过取出事件属性传参的方式;
        // 因为debounce中,函数在setTimeout异步函数中调用,无法访问事件的属性
        onMouseMove={(e) => this.handleChangeDebounce({x: e.clientX, y: e.clientY})}>
          将鼠标在该区域移动
      </div>
    )
  }
}

模拟实现debounce源码:

class App extends React.Component {
  constructor(props){
    super(props);
    this.handleMouseOverDebounced = this.debounce(this.handleMouseMove, 250)
  }
  // 发布改变
  handleMouseMove = (e) => {
    console.log('done',e.clientX)
  }
  // 简单模拟_.debounce
  debounce = (fn, time, {leading=false, trailing=true}={}) => {
    const self = this;
    return function(e) {
      // 为了异步函数中能够访问事件属性
      // e.persist()将合成事件从事件池移除,保留对原生事件对象的引用
      // 如果不需要访问事件属性,去掉该行代码
      e.persist(); 
      if (self.timer) {
        clearTimeout(self.timer);
      }
      self.timer = setTimeout(() => {
        // 异步函数
        fn.call(self, e)
      },time)
    }
  }
  componentWillUnmount() {
    clearTimeout(this.timer);
  }
  render() {
    const style ={width:300, height:300,border:'1px solid red'};
    return (
      <div onMouseMove={this.handleMouseOverDebounced} style={style}>
        请将鼠标在此处移动
      </div>
    )
  }
}

2.节流(throttle)

节流:  基于时间的频率,抽样更改。

原理:指定一个时间间隔timeSpace作为节流的频率。如果用户距离上次更改时间>=timeSpace,

        触发更新;否则不触发。

示例:(_.throttle)

默认配置:

{leading: true, trailing: true}
//  默认第一次和最后一次都发布改变

代码如下:

import {throttle} from 'lodash';

class App extends React.Component {
  constructor(props){
    super(props);
    this.handleChangeThrottled = throttle(this.handleChange, 1000)
  }
  componentWillUnmount() {
    this.handleChangeThrottled.cancel();
  }
  handleChange = () => {
    console.log('done');
  }
  render() {
    const style ={width:300, height:300,border:'1px solid red'};
    return (
      <div style={style} onMouseMove={this.handleChangeThrottled}></div>
    )
  }
}

模拟实现throttle函数

class App extends React.Component {
  constructor(props){
    super(props);
    this.handleChangeThrottled = this.throttle(this.handleChange, 1000)
  }
  componentWillUnmount() {
    this.handleChangeThrottled.cancel();
  }
  handleChange = () => {
    console.log('done');
  }
  throttle = (fn, timeSpace, {leading=true, trailing=true}={}) => {
    const self = this;
    let initialTime = leading ? 0 : new Date().getTime();
    return function(...args) {
      const now = new Date().getTime();
      // 距离上次触发经过的时间   
      const duration = now - initialTime;
      if (duration > timeSpace) {
        // 如果timeSpace时间内再次触发,说明不是最后一次,清空
        if (self.timer) {
          clearTimeout(self.timer);
          self.timer = null; //否则定时器一直递增
        }
        fn.call(self,...args);
        initialTime = now;
      } else if(trailing && !self.timer) {
        // 因为第一次触发的initialTime是0,则remaining为负数,立即触发
        const remaining = timeSpace - duration;
        // timer用于最后一次触发的定时器
        self.timer = setTimeout(() => {
          fn.call(self,...args)
        }, remaining)
      }
    }
  }
  componentWillUnmount() {
    clearTimeout(this.timer);
    this.timer = null
  }
  render() {
    const style ={width:300, height:300,border:'1px solid red'};
    return (
      <div 
        style={style} 
        onMouseMove={this.handleChangeThrottled}
      >
        请将鼠标在该区域移动,查看打印日志
      </div>
    )
  }
}

3. RequestAnimationFrame节流

定义: 基于RequestAnimationFrame的抽样更改。

原理: window.requestAnimationFrame是浏览器中排队等待执行的一种方法,可以在浏览器呈现性能最佳的事件执行。

          它接受一个函数作为参数,将该函数放入requestAnimationFrame的队列,在浏览器的下一帧触发。

          浏览器一般确保每秒60帧。         

应用: 它适用了scroll或者resize等高频触发的事件。

          不适用于输入事件,因为输入事件和动画帧(该方法)基本以相同的速率触发。

          没有意义。输入事件可以使用throttle方法进行节流。

示例:(raf-schd)

import rafSchedule from 'raf-schd';

class App extends React.Component {
  constructor(props){
    super(props);
    // rafSchedule本质上就是一个高阶函数
    this.handleScrollRaf = rafSchedule(this.handleChange)
  }
componentWillUnMount() {
this.handleScrollRaf.cancel()
} handleChange
= (points) => { console.log(points); } handleScroll = (e) => { // 此处无法访问e.clientX;可以访问target,type等属性 this.handleScrollRaf({x: e.target.scrollWidth, y: e.target.scrollHeight}) } render() { const style ={width:300, height:300,border:'1px solid red',overflow: 'auto'}; return ( <div onScroll={this.handleScroll} style={style}> <div style={{height: 3000,background: '#ff0'}}> 请在此块区域滚动滚动条 </div> </div> ) } }

 

posted @ 2019-09-05 12:00  Lyra李  阅读(312)  评论(0编辑  收藏  举报