什么是防抖、节流,分别解释一下?
在白纸上手写一个防抖or节流函数,自己任选(限时4分钟)
react hooks有了解吗?上机实现一个useDebounce、 useThrottle
tyepscript有了解吗?用ts再来写一遍
防抖(debounce)和节流(throttle)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。
debounce
把触发非常频繁的事件(比如按键)合并成一次执行
debounce 又分为前缘debounce(leading) 和后缘debounce(trailing)
1 2 3 4 5 6 7 8
| npm i -g lodash-cli // /usr/local/bin/lodash -> /usr/local/lib/node_modules/lodash-cli/bin/lodash lodash include=debounce,throttle
var debounced_version = _.debounce(doSomething, 200); $(window).on('scroll', debounced_version); // 如果需要的话 debounced_version.cancel();
|
1 2 3 4 5 6 7 8 9
| export function debounce(func: Function, timeout: number) { let timer: NodeJS.Timeout; return (...args: any) => { clearTimeout(timer); timer = setTimeout(() => { func(...args); }, timeout); }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| function debounce(fn, ms) { let timer; return function(...args) { if(timer) { clearTimeout(timer); } timer = setTimeout(() => { fn(...args); timer = null; }, ms) } }
// 测试用例1 export default function() { const [counter, setCounter] = useState(0); const handleClick = useDebounce(function() { setCounter(counter + 1) }, 1000) return <div style={{ padding: 30 }}> <Button onClick={handleClick} >click</Button> <div>{counter}</div> </div> }
// 测试用例2 export default function() { const [counter1, setCounter1] = useState(0); const [counter2, setCounter2] = useState(0); const handleClick = useDebounce(function() { console.count('click1') setCounter1(counter1 + 1) }, 500) useEffect(function() { const t = setInterval(() => { setCounter2(x => x + 1) }, 500); return clearInterval.bind(undefined, t) }, []) return <div style={{ padding: 30 }}> <Button onClick={function() { handleClick() }} >click</Button> <div>{counter1}</div> <div>{counter2}</div> </div> }
// wrong // 每次组件重新渲染,都会执行一遍所有的hooks,这样debounce高阶函数里面的timer就不能起到缓存的作用(每次重渲染都被置空)。timer不可靠,debounce的核心就被破坏了。 function useDebounce(fn, time) { return debounce(fn, time); }
// correct function useDebounce(fn, delay, dep = []) { const { current } = useRef({ fn, timer: null }); useEffect(function () { current.fn = fn; }, [fn]);
return useCallback(function f(...args) { if (current.timer) { clearTimeout(current.timer); } current.timer = setTimeout(() => { current.fn.call(this, ...args); }, delay); }, dep) }
// correct function useThrottle(fn, delay, dep = []) { const { current } = useRef({ fn, timer: null }); useEffect(function () { current.fn = fn; }, [fn]);
return useCallback(function f(...args) { if (!current.timer) { current.timer = setTimeout(() => { delete current.timer; }, delay); current.fn.call(this, ...args); } }, dep); }
|
throttle
只允许一个函数在x毫秒内执行一次
跟 debounce 主要的不同在于,throttle 保证 X 毫秒内至少执行一次
1
| _.throttle(dosomething, 16)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function throttle(func, timeout) { let ready: boolean = true; return (...args) => { if (!ready) { return; }
ready = false; func(...args); setTimeout(() => { ready = true; }, timeout); }; }
|
requestAnimationFrame
可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。
优点
- 动画保持 60fps(每一帧 16 ms),浏览器内部决定渲染的最佳时机
- 简洁标准的 API,后期维护成本低
缺点
- 动画的开始/取消需要开发者自己控制,不像 ‘.debounce’ 或 ‘.throttle’由函数内部处理。
- 浏览器标签未激活时,一切都不会执行。
- 尽管所有的现代浏览器都支持 rAF ,IE9,Opera Mini 和 老的 Android 还是需要打补丁。
- Node.js 不支持,无法在服务器端用于文件系统事件。