防抖和节流
节流和防抖都是为了防止函数调用太快或者太多次。
其中_.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> ) } }