前言
最近面试被问到了闭包和防抖,之前只是知道概念,但没有深刻理解,被面试官提醒才明白防抖就是闭包的一个实际应用场景,于是深入学习,总结如下。
闭包
- 简单来说,能够读取其他函数内部变量的函数就是闭包。
- 闭包的产生其实来自于JavaScript的变量作用域。在JavaScript中,变量的作用域属于函数作用域,当函数执行完成之后,作用域就会被清理,内存也就随之被回收。但是由于闭包函数是建立在函数内部的子函数,闭包函数又能够访问父函数的变量,所以就会导致当父函数执行完成时,其作用域不会销毁,该变量永久的保存在内存中直到闭包函数也不存在时才进行销毁。
防抖与节流
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。我们可以用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
-
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
-
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
代码实现
函数防抖
function debounce(fn, delay) {
let timer = null;
return function() {
let context = this;
let arg = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, arg);
}, delay);
};
}
以闭包角度解释:
- timer就是闭包函数调用的外部变量
- arguments是每一个funcation函数都会有的对象,在函数调用时,浏览器每次都会传递进两个隐式参数this和arguments,封装实参的对象arguments,在ES6中的箭头函数中使用…rest作为arguments.
- 函数debounce 返回的匿名函数调用了timer,导致timer脱离debounce函数的作用域存活于内存中,直到匿名函数也执行完毕,才会被回收。故当点击间隔小于delay毫秒时,timer就会不断更新值,导致setTimeout内的匿名函数无法执行(因为setTimeout内的函数会延迟delay毫秒执行),直到没有新的调用事件时,fn才会正常延迟到delay毫秒后执行
函数节流
function throttle(fn,delay){
// 记录上一次函数出发的时间
let lastTime = 0
return function(){
// 记录当前函数触发的时间
let nowTime = new Date().getTime()
let context = this;
// 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数
if(nowTime - lastTime > delay){
// 绑定this指向
fn.call(this)
//同步时间
lastTime = nowTime
}
}
}