和防抖不同,节流操作是指在连续触发某一事件的情况下,每隔一段时间就触发一次事件,时间固定,具体的使用场景和防抖的类似,不过我一般使用防抖,可以节省请求。
第一种方案:利用时间戳,直接执行
实现思路:如果当前时间减去上一次触发事件大于间隔事件,触发函数,并将当前时间赋值为记录时间,否则就无事发生
操作:当鼠标滑过某个区域后,停止滑动后,3000ms,计数器加一
export const throttle = (fn, wait) => {
var context = this;
// 变量:记录上一次时间, 初始化为0,会立即执行
var previous = 0;
return (...args)=>{
// 获取当前毫秒数
var now = +new Date()
// 如果当前时间减去上一次触发事件大于间隔事件,触发函数,并将当前时间赋值为记录时间
if(now - previous > wait){
fn.apply(context, args)
previous = now
}
}
}
import React from 'react';
import { throttle } from '@/util/plugins'
import './index.scss';
class Throttle extends React.Component {
constructor(props){
super(props);
this.state = {
count: 0
}
this.onMouseMove = throttle(this.onMouseMove, 3000)
}
onMouseMove = (a) => {
this.setState(prev => ({
count: prev.count + 1
}), ()=>{
console.log(this.state.count)
})
}
render() {
return <React.Fragment>
<div className="throttle-wrap" onMouseMove={this.onMouseMove.bind(this, 'a')}> </div>
</React.Fragment>
}
}
export default Throttle;
第二种方案:利用定时器,wait时间后执行
实现思路:判断定时器是否存在,若不存在,新建定时器,wait时间后执行,执行后置定时器为null,若存在,无事发生
操作和页面同第一种方案
export const throttle = (fn, wait) => {
var context = this;
var timeout = null;
return (...args) => {
if(!timeout){
timeout = setTimeout(()=>{
timeout = null
fn.apply(context, args)
}, wait)
}
}
}
第三种方案:前两种方案结合,在开始时执行,结尾也执行
大体实现思路:计算剩余触发时间,若剩余触发时间<0,则立刻触发事件,否则的情况,如果没有定时器,则新建定时器,执行后置定时器为null,具体细节见函数
操作和页面同第一种方案
export const throttle = (fn, wait) => {
var timeout = null, context = this;
var previous = 0;
var throttled = (...args) => {
var now = +new Date();
// 下次触发fn的剩余时间
var remaining = wait - (now - previous);
// 如果没有剩余的时间了, remain>wait,用户在执行期间,回调时间,几乎不可能出现
if(remaining <= 0 || remaining > wait){
// 若有定时器,清除
if(timeout){
clearTimeout(timeout)
timeout = null
}
previous = now
fn.apply(context, args)
}else if(!timeout){
timeout = setTimeout(()=>{
previous = now;
timeout = null;
fn.apply(context, args)
}, remaining);
}
}
return throttled
}
第四种方案:基于方案三,提供option,
实现效果:可以选择使用方案一({trailing: false})、方案二({leading: false})、方案三(三者互斥),补充解除节流属性cancel
export const throttle = (fn, wait, options) => {
var timeout = null, context = this;
var previous = 0;
if(!options) options = {};
var throttled = (...args) => {
var now = +new Date();
// 不需要一开始执行,则进入timeout
if(!previous && options.leading === false) previous = now
// 下次触发fn的剩余时间
var remaining = wait - (now - previous);
// 如果没有剩余的时间了, remain>wait,用户在执行期间,回调时间,几乎不可能出现
if(remaining <= 0 || remaining > wait){
if(timeout){
clearTimeout(timeout)
timeout = null
}
previous = now
fn.apply(context, args)
if (!timeout) context = null;
}else if(!timeout && options.trailing !== false){
// 若不需要结尾执行,则置trailing为false
timeout = setTimeout(()=>{
previous = options.leading === false ? 0 : +new Date();
timeout = null;
fn.apply(context, args)
if (!timeout) context = null;
}, remaining);
}
}
throttled.cancel = () => {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled
}
import React from 'react';
import { throttle } from '@/util/plugins'
import './index.scss';
class Throttle extends React.Component {
constructor(props){
super(props);
this.state = {
count: 0
}
this.onMouseMove = throttle(this.onMouseMove, 3000, {leading: false})
this.clickBtn = throttle(this.clickBtn, 10000)
}
onMouseMove = (a) => {
this.setState(prev => ({
count: prev.count + 1
}), ()=>{
console.log(this.state.count)
})
}
clickBtn = () => {
console.log('click')
}
render() {
return <React.Fragment>
<div className="throttle-wrap" onMouseMove={this.onMouseMove.bind(this, 'a')} >
</div>
<button onClick={this.clickBtn}>开心按钮</button>
<button onClick={()=>{this.clickBtn.cancel()}}>解除按钮</button>
</React.Fragment>
}
}
export default Throttle;
最后
思路比代码重要
附:前端性能优化——防抖
网友评论