曾经面试时候被问到过这个,年少的我一脸无知。。。前端
后来工做中遇到了一个场景:输入名称的同时去服务器校验名称是否重复,但发现以前的代码居然都没作限制,输入一次发一次请求。简直忍不了,就在项目的utils里加上了防抖函数。
正好作一个总结,加深印象。面试
函数防抖和节流,都是控制事件触发频率的方法。应用场景有不少,输入框持续输入,将输入内容远程校验、屡次触发点击事件、onScroll等等。
为了说明问题,假设一个场景:鼠标滑过一个div,触发onmousemove事件,它内部的文字会显示当前鼠标的坐标。服务器
<style> #box { width: 1000px; height: 500px; background: #ccc; font-size: 40px; text-align: center; line-height: 500px; } </style> <div id="box"></div> <script> const box = document.getElementById('box') box.onmousemove = function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` } </script>
效果是这样的:闭包
在上边的场景下,咱们不但愿触发一次就执行一次,这就要用到防抖或节流。下面咱们看一下它们能为咱们作什么吧。app
函数防抖,这里的抖动就是执行的意思,而通常的抖动都是持续的,屡次的。假设函数持续屡次执行,
咱们但愿让它冷静下来再执行。也就是当持续触发事件的时候,函数是彻底不执行的,等最后一次触发结束的
一段时间以后,再去执行。先看一下效果:函数
分解一下需求:性能
那么怎么实现上述的目标呢?咱们先看这一点:在不触发的一段时间以后再执行,那就须要个定时器呀,定时器里面调用咱们要执行的函数,将arguments传入。
封装一个函数,让持续触发的事件监听是咱们封装的这个函数,将目标函数做为回调(func)传进去,等待一段时间事后执行目标函数this
function debounce(func, delay) { return function() { setTimeout(() => { func.apply(this, arguments) }, delay) } }
第二点实现了,再看第一点:持续触发不执行。咱们先思考一下,是什么让咱们的函数执行了呢?是上边的setTimeout。OK,那如今的问题就变成了
持续触发,不能有setTimeout。这样直接在事件持续触发的时候,清掉定时器就行了。spa
function debounce(func, delay) { let timeout return function() { clearTimeout(timeout) // 若是持续触发,那么就清除定时器,定时器的回调就不会执行。 timeout = setTimeout(() => { func.apply(this, arguments) }, delay) } }
用法:3d
box.onmousemove = debounce(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)
节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。
一样,咱们分解一下:
效果是这样的:
思考一下,持续触发,并不会执行,可是到时间了就会执行。抓取一个关键的点:就是执行的时机。
要作到控制执行的时机,咱们能够经过一个开关,与定时器setTimeout结合完成。
函数执行的前提条件是开关打开,持续触发时,持续关闭开关,等到setTimeout到时间了,再把开关打开,函数就会执行了。
咱们看一下代码怎么实现:
function throttle(func, deley) { let run = true return function () { if (!run) { return // 若是开关关闭了,那就直接不执行下边的代码 } run = false // 持续触发的话,run一直是false,就会停在上边的判断那里 setTimeout(() => { func.apply(this, arguments) run = true // 定时器到时间以后,会把开关打开,咱们的函数就会被执行 }, deley) } }
调用的时候:
box.onmousemove = throttle(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)
这样,就实现了节流,节流还能够用时间间隔去控制,就是记录上一次函数的执行时间,与当前时间做比较,若是当前时间与上次执行时间的时间差大于一个值,就执行。
说明一下节流时,后面操做中应该是由于run=false 因此才直接return,可是不是在return以前let run=ture直接覆盖掉以前的false
这里能够看一下throttle函数内部,和函数调用的时候。首先看函数内部,分解一下结构:
function throttle(func, deley) { return function () { // 执行func } }
那调用时候呢?也分解一下:
throttle(function () { // 目标函数内容 }, 1000)
这里throttle函数执行的结果是其内部return的function的调用。也就是说鼠标通过的事件监听其实是这个被return的function,不断持续触发的是它,而throttle函数只是提供了一个做用域,内部用闭包声明了一个run的开关变量,因为闭包的存在,run这个变量会一直存在不被销毁,而let run = true只在这个闭包(能够理解为做用域)内只声明了一次,但它不会被持续执行,因此return的函数内部的判断不会被它覆盖掉。根据打印结果能够看出,事实确实是如此:
防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优势很明显,能够节约性能,不至于屡次触发复杂的业务逻辑而形成页面卡顿。
欢迎关注个人公众号: 一口一个前端,不按期分享我所理解的前端知识