今天学习的内容让我感觉自己像个魔法师,掌握了两种神奇的编程技巧:闭包赋予变量"隐身术",防抖则教会函数"拖延症"。让我们通过几个有趣的例子,揭开它们的神秘面纱!
场景一:输入框的"话痨症"治疗日记
想象两个输入框兄弟——InputA是个急性子,InputB则是个慢性子:
<input type="text" id="inputA">
<input type="text" id="inputB">
InputA每次按键都急吼吼地喊:"我要发请求!"(控制台惨不忍睹):
inputA.addEventListener('keyup', function(e) {
ajax(e.target.value); // 疯狂输出
})
我们来看效果:
而InputB就聪明多了,它请来了防抖这位"拖延症大师":
let debounceAjax = debounce(ajax, 200); // 200ms冷静期
inputB.addEventListener('keyup', function(e) {
debounceAjax(e.target.value); // 优雅从容
})
有了防抖之后,我们在一定时间内连续输入,只执行最后一次,如果这是一个真正的请求后端数据的函数,那么相比于上述输入框A,性能是显著提升的,如果我们不做防抖处理,那么我们的用户的输入,就像成千上万的DDOS攻击,会致使我们的服务器瘫痪
那么什么是防抖呢?
是一种编程实践技术,用于确保某个函数不会在短时间内被频繁调用。具体来说,当一个事件被触发时(比如窗口调整大小、滚动、按键或鼠标点击等),如果在设定的时间间隔内该事件再次被触发,则重新计时;只有当事件触发后在指定的时间间隔内没有再次触发时,才会执行相应的处理函数。这种方法可以有效地减少不必要的函数调用次数,从而提高性能和响应速度。
防抖的优点包括:
- 减少计算资源消耗:通过限制函数的调用频率,减少了CPU和内存的使用,特别是在处理高频率触发的事件(如resize, scroll, keypress等)时非常有效。
- 提升用户体验:避免了由于频繁触发事件而导致的界面卡顿现象,使得用户交互更加流畅。
- 防止服务器过载:在网络请求中应用防抖技术,可以减少不必要的请求发送,减轻服务器负担,尤其是在搜索框输入实时查询这种场景下特别有用。
- 简化逻辑处理:对于一些只需要最后一次操作结果的情况,使用防抖可以忽略中间状态,直接获取最终结果,简化逻辑流程。
防抖函数的魔法在于它的"拖延策略":
function debounce(fn, delay) {
return function(args) {
clearTimeout(fn.id); // 打断前一次施法
fn.id = setTimeout(() => {
fn(args); // 延迟执行
}, delay);
}
}
当继续触发,会检测定时器是否存在,存在就清除掉,也就是说,只有最后一次定时器的回调函数才会执行
我们可以来看看,实际应用场景,在浏览器中,当用户输入内容,有下拉搜索框提示用户,但当用户快速输入,就不会触发(也就是google 发家致富的 suggest 用户建议输入):
防抖哲理:用户输入时就像在说rap,我们不需要每个音节都响应,等他们喘气的间隙再行动才最明智!
场景二:this丢失案发现场
当防抖遇到对象方法时,发生了离奇的this失踪案:
let obj = {
count: 0,
inc: debounce(function(val) {
console.log(this);
console.log(this.count += val); // this神秘消失!
}, 500)
}
可以看到执行结果,this指向了我们全局对象window,这是因为,this丢失,我们定时器内部的函数被当作普通函数执行了,this也就会指向全局对象,如果大家不清楚this指向,可以看看这篇文章JavaScript中的this指向:从懵圈到豁然开朗的奇幻之旅 这是我之前在学习this指向写的一篇文章,希望对于正在学this指向的你有所帮助
破案的关键在于保存案发现场:
function debounce(fn, delay) {
return function(args) {
let that = this; // 案发现场快照!
clearTimeout(fn.id);
fn.id = setTimeout(() => {
fn.call(that, args); // 用call还原现场
}, delay);
}
}
返回的函数就是我们对象中inc方法我们可以用that接住方法的this,也就是我们的对象,让我们定时器中的函数this指向我们的对象,这也就解决的this丢失问题
侦探笔记:箭头函数是
this的保镖,普通函数是this的迷路小孩,记得用call或apply送它回家!
场景三:计数器的隐私保卫战
这个计数器有点害羞,它的count变量不想被外人知道:
function CreateCounter(num) {
let count = 0 // 藏在闭包里的秘密
return {
num: num, // 公开部分
increment: () => count++, // 操作接口
getCount: () => {
console.log('有人偷看我的日记!');
return count;
}
}
}
const counter = CreateCounter(1);
counter.increment();
console.log(counter.count); // undefined - 隐私保护成功!
console.log(counter.getCount()); // 1 - 需经授权访问
闭包哲学:闭包就像保险柜,变量锁在里面,只通过特定接口访问。既保护隐私又提供控制!
闭包应用全家桶(知识点速记)
1. 记忆函数 - 函数得了"好记性"
2. 柯里化 - "分步骤喂食"函数
3. 防抖 - "等等党"函数代表
4. 节流 - "匀速运动"爱好者
5. 私有变量 - 变量的"隐身衣"
6. 偏函数 - 函数的"预制菜"
当然在这篇文章我还没有来得及介绍我们的节流,之后会继续学习给大家介绍
冷知识:防抖和节流的区别就像:
- 防抖:等电梯(最后一个人进来才关门)
- 节流:水龙头(匀速滴水)
防抖在实战中的奇妙变身
根据使用场景调整防抖策略:
// 经典防抖(发请求场景)
const searchDebounce = debounce(ajax, 300);
// 立即执行版(按钮提交场景)
function debounceImmediate(fn, delay) {
let timer;
return function() {
if(!timer) fn.apply(this, arguments); // 首次立即执行
clearTimeout(timer);
timer = setTimeout(() => timer = null, delay);
}
}
适用场景对照表:
场景 策略 延迟 搜索建议 经典防抖 300ms 窗口resize 经典防抖 200ms 按钮提交 立即执行版 1000ms 无限滚动 节流 500ms
闭包与面向对象的"跨界合作"
闭包实现私有变量与Class语法对比:
// 闭包版(老派做法)
function CoffeeMachine() {
let water = 100; // 私有
return {
brew: () => water -= 10
}
}
// Class版(新潮做法)
class CoffeeMachine {
#water = 100; // 私有属性
brew() { this.#water -= 10; }
}
历史小剧场:JS早期没有class关键字时,程序员们用闭包伪造私有变量,就像用纸板造坦克——外表唬人,实际易燃!
总结:闭包防抖三定律
- 节能定律:防抖是函数的节能模式,避免无意义的性能消耗
- 隐私定律:闭包是变量的保险柜,钥匙由你掌控
- 上下文定律:丢失
this时,call/apply就是时空传送门
最后记住这个防抖口诀:
"频繁事件别慌张,
闭包里面藏定时。
清除上次保当前,
延迟执行最明智!"
闭包和防抖的搭配就像咖啡和奶泡——分开也能用,合起来才完美!现在就去给你的项目加点"拖延症"和"隐私保护"吧~