防抖和节流
前言
如果一段代码被多次请求或调用,可能会导致以下结果:
资源浪费:多次请求或调用会消耗更多的计算资源和内存,造成资源浪费。特别是对于复杂、耗时的操作,频繁的请求或调用可能会导致系统负荷过大。
执行速度变慢:如果一段代码被多次请求或调用,并且每次请求或调用都需要执行一定的计算或操作,那么代码执行的速度会变慢。这可能会导致响应时间延长,用户体验下降。
不一致的结果:某些代码可能具有副作用,例如修改全局状态或数据库操作。多次请求或调用可能导致竞争条件或不一致的结果。例如,多个并发请求同时对同一资源进行修改,可能导致数据混乱或错误的结果。
冲突和错误:某些代码可能依赖于特定的执行顺序或条件。如果多次请求或调用改变了这些顺序或条件,可能会导致冲突和错误的结果。例如,多个并发请求同时更新相同的数据,可能导致数据冲突或错误的更新。
安全性问题:如果一段代码处理敏感数据或进行安全验证,多次请求或调用可能增加安全性风险。例如,重复请求可能导致重复提交或未经授权的操作。
为避免上述问题,可以使用防抖和节流等技术来限制代码的执行频率,避免过于频繁的请求或调用。另外,还可以对代码进行优化和合理设计,确保代码的可重入性和线程安全性,避免冲突和错误的结果。
1. 防抖
1.1 什么是防抖
防抖是一种用于处理频繁触发的事件的技术。它通过延迟触发事件并在规定时间内取消事件的连续触发来解决问题。主要用于防止意外的多次触发。

1.2 原理
防抖的原理是在事件被触发后,设置一个定时器,并在规定的时间内重置定时器。如果在规定时间内再次触发了事件,则重置定时器,直到事件触发的间隔超过规定时间,才执行事件处理函数。
1.3 应用场景
- 防止按钮连续点击导致重复操作
- 处理输入框实时搜索,避免频繁发送请求
- 监听窗口大小变化等事件
1.4 实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function debounce(func, delay) { let timeoutId;
return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; }
function printText(text) { console.log(text); }
const debouncedPrint = debounce(printText, 500);
debouncedPrint("Hello"); debouncedPrint("World"); debouncedPrint("OpenAI");
|
在这个示例中,定义了一个防抖函数debounce
,它接受一个函数和延迟时间作为参数,并返回一个新的函数。新函数使用setTimeout
延迟执行传入的函数,并在每次调用时清除之前的定时器,确保在延迟时间内没有新的调用才执行函数。
然后,定义了一个示例函数printText
,它会打印输入的文本。通过使用防抖函数debounce
,创建了一个防抖后的打印函数debouncedPrint
,该函数在连续触发事件时,每次输入文本后的500毫秒内没有新的输入才会执行打印操作。
在示例中,模拟了连续触发事件的情况,每次输入文本后会等待500毫秒,如果在这段时间内没有新的输入,才会执行打印操作。这样可以避免频繁的打印操作,只在用户输入稳定一段时间后才执行实际的打印操作,提高代码的执行效率。
1.5 淘宝输入框的防抖
1.51 防抖的应用场景


1.52 实现输入框的防抖

1.53 完整代码
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> <link rel="stylesheet" href="./css/search.css" /> <script src="./lib/template-web.js"></script> <script src="./lib/jquery.js"></script> </head>
<body> <div class="container"> <img src="./images/taobao_logo.png" alt="" class="logo" />
<div class="box"> <div class="tabs"> <div class="tab-active">宝贝</div> <div>店铺</div> </div> <div class="search-box"> <input type="text" id="ipt" class="ipt" placeholder="请输入要搜索的内容" /> <button class="btnSearch"> 搜索 </button> </div> <div id="suggest-list"></div> </div> </div>
<script type="text/html" id="suggest-items"> {{each result}} <div id="suggest-item">{{$value[0]}}</div> {{/each}} </script>
<script> $(function () { var timer = null function debounceSearch(keywords) { timer = setTimeout(function () { getSuggestList(keywords) }, 500) }
$('#ipt').on('keyup', function () { clearTimeout(timer)
const txt = $(this).val().trim()
if (txt.length <= 0) { return $('#suggest-list').empty().hide() }
debounceSearch(txt) })
function getSuggestList(goods) { $.ajax({ type: 'GET', url: 'https://suggest.taobao.com/sug?q=' + goods, dataType: 'jsonp', jsonp: 'callback', success: function (res) { renderSuggestList(res) } }) }
function renderSuggestList(res) { if (res.result.length <= 0) { return $('#suggest-list').empty().hide() } const htmlStr = template('suggest-items', res) $('#suggest-list').html(htmlStr).show() }
}) </script> </body>
</html>
|

1.54 缓存优化



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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> <link rel="stylesheet" href="./css/search.css" /> <script src="./lib/template-web.js"></script> <script src="./lib/jquery.js"></script> </head>
<body> <div class="container"> <img src="./images/taobao_logo.png" alt="" class="logo" />
<div class="box"> <div class="tabs"> <div class="tab-active">宝贝</div> <div>店铺</div> </div> <div class="search-box"> <input type="text" id="ipt" class="ipt" placeholder="请输入要搜索的内容" /> <button class="btnSearch"> 搜索 </button> </div> <div id="suggest-list"></div> </div> </div>
<script type="text/html" id="suggest-items"> {{each result}} <div id="suggest-item">{{$value[0]}}</div> {{/each}} </script>
<script> $(function () { var timer = null function debounceSearch(keywords) { timer = setTimeout(function () { getSuggestList(keywords) }, 500) }
var cacheObj = {}
$('#ipt').on('keyup', function () { clearTimeout(timer)
const txt = $(this).val().trim()
if (cacheObj[txt]) { return renderSuggestList(cacheObj[txt]) }
if (txt.length <= 0) { return $('#suggest-list').empty().hide() }
debounceSearch(txt) })
function getSuggestList(goods) { $.ajax({ type: 'GET', url: 'https://suggest.taobao.com/sug?q=' + goods, dataType: 'jsonp', jsonp: 'callback', success: function (res) { renderSuggestList(res) } }) }
function renderSuggestList(res) { if (res.result.length <= 0) { return $('#suggest-list').empty().hide() } const htmlStr = template('suggest-items', res) $('#suggest-list').html(htmlStr).show() }
var k = $('#ipt').val().trim() cacheObj[k] = res
}) </script> </body>
</html>
|
2. 节流
2.1 什么是节流
节流是一种用于控制事件触发频率的技术。它通过限制在规定时间内触发事件的次数来解决问题。主要用于限制事件的执行频率。

2.2 原理
节流的原理是在事件被触发后,设置一个定时器,在规定的时间内只能触发一次事件。如果在规定时间内再次触发了事件,则忽略该次触发,直到规定时间过去后才能再次触发事件。
2.3 应用场景
- 监听滚动事件,限制触发频率
- 处理鼠标移动、拖拽等连续触发的事件
- 监听输入框输入事件,限制频繁的输入回调

2.4 实现方法
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
| function throttle(func, delay) { let timeoutId; let lastExecTime = 0;
return function (...args) { const currentTime = Date.now();
if (currentTime - lastExecTime > delay) { func.apply(this, args); lastExecTime = currentTime; } else { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); lastExecTime = Date.now(); }, delay); } }; }
function printTime() { console.log(new Date().toLocaleTimeString()); }
const throttledPrint = throttle(printTime, 1000);
setInterval(throttledPrint, 200);
|
在这个示例中,定义了一个节流函数throttle
,它接受一个函数和时间间隔作为参数,并返回一个新的函数。新函数在触发时会检查当前时间与上次执行的时间间隔,如果超过了设定的时间间隔,就执行传入的函数,并更新上次执行的时间;如果没有超过时间间隔,就通过setTimeout
延迟执行传入的函数,保证在固定的时间间隔内执行一次。
然后,定义了一个示例函数printTime
,它会打印当前的时间。通过使用节流函数throttle
,创建了一个节流后的打印函数throttledPrint
,该函数每隔1秒执行一次打印操作。
在示例中,使用setInterval
模拟了连续触发事件的情况,每次触发都会在1秒钟内执行一次打印操作。由于节流函数的限制,即使在间隔时间内连续触发事件,也只会在固定的时间间隔内执行一次打印操作。这样可以控制打印操作的频率,避免过于频繁的执行,保持合理的时间间隔,提高代码的执行效率。
2.5 节流阀的概念

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
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head>
<body> <script>
var timer = null function stack() { console.log(Boolean(null)); if (timer) return timer = setTimeout(function () { console.log('stack overflow'); timer = null stack() }, 16)
} stack() </script> </body>
</html>
|
2.6 节流案例 – 鼠标跟随效果
2.61 不使用节流时实现鼠标跟随效果


2.62 使用节流优化鼠标跟随效果


2.63 完整代码
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
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="/lib/jquery-3.6.0.js"></script> <style> html, body { margin: 0; padding: 0; overflow: hidden; }
#angel { position: absolute; } </style> </head>
<body> <img src="./angel.gif" alt="" id="angel" />
<script> $(function () { var angel = $('#angel') var timer = null $(document).on('mousemove', function (e) { if (timer) { return } timer = setTimeout(function () { $(angel).css('top', e.pageY - angel.height() / 2 + 'px').css('left', e.pageX - angel.width() / 2 + 'px') console.log('ok') timer = null }, 15)
}) }) </script> </body>
</html>
|
3. 区分
防抖(Debounce)和节流(Throttle)是两种常见的函数执行控制技术,它们旨在解决高频率事件触发时造成性能问题的情况。尽管它们都可以限制函数的执行频率,但在实现和效果上存在一些区别。

3.1 相同点
- 控制函数执行频率:防抖和节流都可以控制函数的执行频率,以避免频繁触发和执行函数,减少不必要的计算和资源消耗。
- 提升性能:通过限制函数的执行次数,防抖和节流可以提升页面的响应性能,避免过于频繁的操作导致页面卡顿或浏览器负载过重。
3.2 区别
- 触发时机:防抖在事件触发后的一段时间内不执行函数,只有在事件停止触发一定时间后才执行一次;而节流在固定的时间间隔内执行函数,每隔一定时间执行一次,无论事件是否持续触发。
- 执行次数:防抖只会在事件停止触发后执行一次函数,即使事件持续触发也只会执行一次;而节流在固定时间间隔内,无论事件触发多少次,都会按照设定的时间间隔执行一次函数。
- 响应速度:防抖需要等待一定的延迟时间后才会执行函数,因此在事件触发后会有一定的延迟;而节流会根据设定的时间间隔,立即执行函数,因此响应速度会更快。
- 应用场景:防抖适用于需要等待事件停止后执行的场景,如按钮点击、实时搜索等;而节流适用于需要固定时间间隔执行的场景,如滚动监听、鼠标移动监听等。
综上所述,防抖和节流都是用于控制函数执行频率的方法,但在触发时机、执行次数、响应速度和应用场景上存在一些差异,需要根据具体情况选择适合的方法。