- 在面试中经常会被用到闭包的使用场景,百度的时候很多答案准备的例子都是防抖函数,
- 有的面试官就会说,我不想听这个答案,还有其他例子吗?(毕竟网络是共享的,这个答案准备的比较多,面试官不想听很正常,可以结合自己的项目展开说说)
- 如果你说暂时没想好其他例子,面试官还是会给面子,会继续往下问的
- 那就会问:那就说说防抖为什么要用闭包吧,不用闭包行吗
(这是真实的面试经历,所以为了下次的面试,我还是整理下学习笔记,给以后的自己看吧)
- 首先要理解什么是防抖?
函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
知道了定义,现在就来一步一步的实现吧
// 首先写一个debounce函数吧,
function debounce(){
}
// 拆解概念:
// 1,n 秒内只能执行一次,所以需要一个setTimeout(()=>{fn()},delay),
// 2, 如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。所以n秒内如果再次触发,就清除上一次的函数,那就需要用用一个变量timer 来保存上一个setTimeout返回值,好用clearTimeout来取消执行,存在就清除然后重新计时,大概框架就是
function debounce (fn,delay) {
var timer
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
}, delay)
}
// 然后你会发现 这样写的话每次执行debounce ,timer都会被重新创建,然后执行完debounce后,里面的变量就被销毁了,这样就会创建出很多个延迟没执行的函数,一到时间就执行了,并不会重新计算时间
//接下来做个改造,把timer 提到全局作用域内,
var timer
function debounce (fn, delay) {
if (timer ) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
}, delay)
}
上面的函数虽然也能实现防抖的功能,但是如果有多个要防抖的事件,要定义多个变量,写多个函数吗?
第一个方法之所以要定义多个变量,因为他们共用同一个作用域(全局作用域),如果他们能有自己的作用域,相互之间不影响不就好了。
那就要定一个私有变量,并且能在外部访问,那闭包就是一个选择
function debounce (fn, delay) {
var timer
function inside() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this,arguments)
}, delay)
}
return inside
/*
如果这个函数 不用return 每次点击执行的就是debounce 函数,inside也被立即执行,
debounce执行完之后,里面的变量和inside函数就被销毁了,所以每次点击都会执行debounce函数,
timer都是新创建的,就没办法清除上一个timer
*/
}
/*
如果这里return 一个函数,那就是inside在没执行的时候就被return 出去了;
过程为:
没有点击的时候debounce就会被先调用, debounce函数被调用的时候,timer初始化,并返回一个inside函数,
但是inside还没有执行,debouce函数被调用后,里面的timer并没有被销毁,因为inside函数还在引用 ,当第一次点击的时候才会触发inside函数,
inside函数执行的时候debounce作用域中timer被赋值,inside执行结束,并不能销毁debounce作用域中的timer,
第二次点击的时候,inside再次执行使用的还是debounce作用域中timer,inside保持了debounce的作用域并有使用权限
当在n秒内再次触发点击可以判断上个定时器在不在,在的话就要先清除,重新赋值并重新计时,
*/
上面的例子因为是setTimeout 用的是 箭头函数 所以this和上层的this同一个指向,把this指向执行执行环境,要不然fn中的this指向全局变量
- 如果setTimeout(function(){ fn() }, delay) 不使用箭头函数 请参考下面的写法(this指向问题这里就不做解释了)
function debounce (fn, delay) {
var timer
function inside() {
clearTimeout(timer)
const that=this
const args= arguments
timer = setTimeout(function(){
fn.apply(that,args)
}, delay)
}
return inside
}
下面是完整例子:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>test</title>
</head>
<body>
<button id="test">点击</button>
</body>
<script>
function debounce (fn, delay) {
var timer
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
var button = document.getElementById('test')
function handleClickAA () {
console.log(this, 'aa')
console.log('我点击了aa')
}
button.addEventListener('click', debounce(handleClickAA, 2000))
</script>
</html>
闭包的优点:模块化了代码, 没有污染全局变量,延长了形参的生命周期