Open libin1991 opened 6 years ago
这是个老生常谈的话题了,之所以还搬出来讲讲,原因之一是之前根本就没在意,近期面临的一些问题需要用到这两个小技巧;原因之二,这两个技巧带来的优化不小;原因之三,顺便复习一下闭包。
开发中你可能会遇到下面的情况:
Window
resize
scroll
mousemove
markdwon
html
第一种和第三种情况,事件短时间内被频繁出发,如果在事件中有大量的计算,频繁操作DOM,资源加载等重行为,可能会导致UI卡顿,严重点甚至让浏览器挂掉。对于第四种情况,有的开发者保存编辑好的文件喜欢按多次Ctrl+S,若是快速的重启服务还能Hold住,但是要是重启一个应用,就可能多次不必要的重启。
Ctrl+S
针对上面这一系列的需求,于是有了debounce和throttle两种解决办法。
debounce
throttle
函数按照一个周期执行,例如给window绑定一个resize事件之后,只要窗口改变大小改变就打印1,如果不采用函数节流,当我们将窗口调节的时候发现控制台一直打印1,但是使用了函数节流后我们会发现调节的过程中,每隔一段时间才打印1。
window
1
一个函数节流的简单实现:
/** * * @param func {Function} 实际要执行的函数 * @param wait {Number} 执行间隔,单位是毫秒(ms),默认100ms * * @return {Function} 返回一个“节流”函数 */ function throttle(func, wait = 100) { // 利用闭包保存定时器和上次执行时间 let timer = null; let previous; // 上次执行时间 return function() { // 保存函数调用时的上下文和参数,传递给 fn const context = this; const args = arguments; const now = +new Date(); if (previous && now < previous + wait) { // 周期之中 clearTimeout(timer); timer = setTimeout(function() { previous = now; func.apply(context, args); }, wait); } else { previous = now; func.apply(context, args); } }; }
使用的方法也很简单:
const btn = document.getElementById('btn'); function demo() { console.log('click'); } btn.addEventListener('click', throttle(demo, 1000));
看看React中怎么使用的,下面监听窗口的resize和输入框的onChange事件:
onChange
import React, { Component } from 'react'; import { throttle } from '../../utils/utils'; export default class Demo extends Component { constructor() { super(); this.change = throttle((e) => { console.log('throttle'); }, 100); } componentDidMount() { window.addEventListener('resize', throttle(this.onWindowResize, 60)); } componentWillUnmount() { window.removeEventListener('resize', throttle(this.onWindowResize, 60)); } onWindowResize = () => { console.log('resize'); } handleChange = (e) => { e.persist(); this.setDragWidth(e); } render() { return ( <input onChange={this.change} /> ); } }
当事件触发之后,必须等待某一个时间(N)之后,回调函数才会执行,假若再等待的时间内,事件又触发了则重新再等待时间N,直到事件N内事件不被触发,那么最后一次触发过了事件N后,执行函数。
还是窗口resize,如果一直改变窗口大小,则不会打印1,只有停止改变窗口大小并等待一段时间后,才会打印1。
函数去抖简单实现:
/** * @param func {Function} 实际要执行的函数 * @param delay {Number} 延迟时间,单位是毫秒(ms) * @return {Function} */ function debounce(fn, delay = 1000) { let timer; // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 func 函数 return function () { // 保存函数调用时的上下文和参数,传递给func var context = this var args = arguments // 函数被调用,清除定时器 clearTimeout(timer) // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作), // 再过 delay 毫秒就执行 func timer = setTimeout(function () { fn.apply(context, args); }, delay); } }
应用场景,监听文件变化,重启应用:
const debounce = require('./debounce'); watcher.on('change', debounce(() => { const child = spawn('npm', ['run', 'dev:electron'], { cwd, detached: true, stdio: 'inherit' }) child.unref(); electron.app.quit(); }, delay));
前言
这是个老生常谈的话题了,之所以还搬出来讲讲,原因之一是之前根本就没在意,近期面临的一些问题需要用到这两个小技巧;原因之二,这两个技巧带来的优化不小;原因之三,顺便复习一下闭包。
开发中你可能会遇到下面的情况:
Window
对象的resize
,scroll
事件mousemove
markdwon
转换成html
第一种和第三种情况,事件短时间内被频繁出发,如果在事件中有大量的计算,频繁操作DOM,资源加载等重行为,可能会导致UI卡顿,严重点甚至让浏览器挂掉。对于第四种情况,有的开发者保存编辑好的文件喜欢按多次
Ctrl+S
,若是快速的重启服务还能Hold住,但是要是重启一个应用,就可能多次不必要的重启。针对上面这一系列的需求,于是有了
debounce
和throttle
两种解决办法。函数节流
函数按照一个周期执行,例如给
window
绑定一个resize
事件之后,只要窗口改变大小改变就打印1,如果不采用函数节流,当我们将窗口调节的时候发现控制台一直打印1
,但是使用了函数节流后我们会发现调节的过程中,每隔一段时间才打印1
。一个函数节流的简单实现:
/** * * @param func {Function} 实际要执行的函数 * @param wait {Number} 执行间隔,单位是毫秒(ms),默认100ms * * @return {Function} 返回一个“节流”函数 */ function throttle(func, wait = 100) { // 利用闭包保存定时器和上次执行时间 let timer = null; let previous; // 上次执行时间 return function() { // 保存函数调用时的上下文和参数,传递给 fn const context = this; const args = arguments; const now = +new Date(); if (previous && now < previous + wait) { // 周期之中 clearTimeout(timer); timer = setTimeout(function() { previous = now; func.apply(context, args); }, wait); } else { previous = now; func.apply(context, args); } }; }
使用的方法也很简单:
const btn = document.getElementById('btn'); function demo() { console.log('click'); } btn.addEventListener('click', throttle(demo, 1000));
看看React中怎么使用的,下面监听窗口的
resize
和输入框的onChange
事件:import React, { Component } from 'react'; import { throttle } from '../../utils/utils'; export default class Demo extends Component { constructor() { super(); this.change = throttle((e) => { console.log('throttle'); }, 100); } componentDidMount() { window.addEventListener('resize', throttle(this.onWindowResize, 60)); } componentWillUnmount() { window.removeEventListener('resize', throttle(this.onWindowResize, 60)); } onWindowResize = () => { console.log('resize'); } handleChange = (e) => { e.persist(); this.setDragWidth(e); } render() { return ( <input onChange={this.change} /> ); } }
函数去抖
当事件触发之后,必须等待某一个时间(N)之后,回调函数才会执行,假若再等待的时间内,事件又触发了则重新再等待时间N,直到事件N内事件不被触发,那么最后一次触发过了事件N后,执行函数。
还是窗口
resize
,如果一直改变窗口大小,则不会打印1,只有停止改变窗口大小并等待一段时间后,才会打印1。函数去抖简单实现:
/** * @param func {Function} 实际要执行的函数 * @param delay {Number} 延迟时间,单位是毫秒(ms) * @return {Function} */ function debounce(fn, delay = 1000) { let timer; // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 func 函数 return function () { // 保存函数调用时的上下文和参数,传递给func var context = this var args = arguments // 函数被调用,清除定时器 clearTimeout(timer) // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作), // 再过 delay 毫秒就执行 func timer = setTimeout(function () { fn.apply(context, args); }, delay); } }
应用场景,监听文件变化,重启应用:
const debounce = require('./debounce'); watcher.on('change', debounce(() => { const child = spawn('npm', ['run', 'dev:electron'], { cwd, detached: true, stdio: 'inherit' }) child.unref(); electron.app.quit(); }, delay));