🧩 闭包的七十二变
🎁 记忆函数 - 让计算像快递一样准时
💡 核心概念
闭包(Closure,别闭嘴!)就像一个带私房钱的保姆,她能记住你家的存款密码(外部变量),即使你换锁了(函数执行完毕)也能随时取钱。
// 记忆函数:像快递柜存包裹
function memoize(fn) {
const cache = {}; // 保姆的私房钱
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) return cache[key]; // 直接取快递
return (cache[key] = fn.apply(this, args)); // 存新快递
};
}
const add = memoize((a, b) => a + b);
console.log(add(1, 2)); // 第一次计算
console.log(add(1, 2)); // 直接取缓存 🚀
文字流程图
用户调用 -> 检查快递柜(cache) -> 有包裹就直接取 📦
-> 没包裹就下单计算 🛒
-> 把结果存进快递柜 📥
常见误区预警
⚠️ 别踩这个坑!如果你用普通对象存储缓存,可能会被GC回收导致"私房钱被偷"。使用闭包就能永久保存记忆。
🧱 类封装 - 隐藏家务的魔法盒子
💡 核心概念
类就像带密码的保险箱,对外只展示取款机(public API),私有财产(private variables)只能通过内部方法操作。
// 类封装:家庭账本管理系统
class FamilyAccount {
#balance = 0; // 私有财产(用#标记)
deposit(amount) {
this.#balance += amount; // 正常收入
}
secretExpense(amount) {
this.#balance -= amount; // 私密支出
}
showBalance() {
return this.#balance;
}
}
const account = new FamilyAccount();
account.deposit(1000); // 正常存款
account.secretExpense(500); // 隐藏支出 💸
console.log(account.showBalance()); // 500
文字流程图
用户调用公开方法 -> 操作私有属性 -> 返回结果
-> 不能直接访问私有属性(#balance)🔒
性能对比
- 普通对象:属性直接暴露 → 安全风险 🔥
- 类封装:属性私有化 → 提升代码安全性 ✅
⚡️ 防抖节流大作战
🤖 防抖:快递员的等待游戏
💡 核心概念
防抖就像快递员等电梯:用户频繁下单(输入搜索词)→ 快递员(AJAX请求)会一直等待,直到5分钟内没新订单才出发送货。
// 防抖:快递员等电梯
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer); // 取消之前的等待
timer = setTimeout(() => {
fn.apply(this, args); // 最终执行
}, delay);
};
}
// 应用场景:实时搜索建议
const inputBox = document.getElementById('searchInput');
const debouncedSearch = debounce((query) => {
console.log(`搜索对乡乡的爱在哪个地方❤️: ${query}`); // 模拟AJAX请求
}, 300);
inputBox.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
文字流程图
用户输入 → 清除旧计时器 ⏳ → 设置新计时器
→ 如果继续输入 → 重置计时器
→ 停止输入后 → 执行最终请求 📦
性能对比
- 未优化:输入10个字符 → 10次请求 🚨
- 防抖优化:输入10个字符 → 1次请求 ✅
🛠️ 节流:打工人的时间管理大师
💡 核心概念
节流就像打工人摸鱼:每小时必须完成3项任务(固定频率),无论领导(用户操作)多么频繁催促。
// 节流:打工人摸鱼
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
fn.apply(this, args); // 执行任务
lastCall = now;
}
};
}
// 应用场景:滚动事件优化
window.addEventListener('scroll', throttle(() => {
console.log('页面滚动中...我对乡乡的爱一直都在...'); // 模拟懒加载
}, 1000));
文字流程图
用户操作 → 检查上次执行时间 🕒
→ 如果间隔足够 → 执行任务 ✅
→ 如果间隔不够 → 跳过本次操作 🙈
性能对比
- 未优化:滚动100次 → 100次执行 🥵
- 节流优化:滚动100次 → 约10次执行 ✅
🧪 代码实战实验室
🧪1. 防抖demo - 带反悔机制的购物车
// 购物车价格计算(防抖+反悔)
function calculateTotalPrice(items) {
// 模拟耗时计算
let total = 0;
items.forEach(item => total += item.price * item.count);
console.log(`总价: ¥${total}`); // 最终结算
}
const cart = {
items: [],
add(item) {
this.items.push(item);
// 防抖计算
debounce(() => calculateTotalPrice(this.items), 500)();
}
};
// 用户疯狂加购
cart.add({ price: 10, count: 2 }); // 过早计算会被取消 😅
cart.add({ price: 20, count: 1 }); // 最终计算 ✅
执行流程动画
用户添加商品 → 启动倒计时 ⏳
→ 继续添加商品 → 重置倒计时
→ 停止添加后 → 最终结算 💰
🧪2. 节流demo - 摸鱼时的完美滑动
// 懒加载图片(节流优化)
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
}
// 节流控制加载频率
window.addEventListener('scroll', throttle(lazyLoadImages, 300));
// 辅助函数:判断元素是否在视口内
function isInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
);
}
性能对比
- 未优化:滚动1000px → 50次加载 🤯
- 节流优化:滚动1000px → 约4次加载 ✅
❓FAQ
Q1: 防抖和节流到底有什么区别?
A: 防抖是「最后一次生效」(快递员等电梯),节流是「每隔一段时间生效」(打工人摸鱼)。就像外卖配送 vs 定时送餐 🍔
Q2: 闭包真的有必要学吗?
A: 闭包是JavaScript的灵魂!它让你能:
- 创建私有变量(家庭保险箱)🔐
- 实现记忆函数(快递柜存包裹)📦
- 封装复杂逻辑(黑盒操作)⚙️
Q3: this指向问题怎么解决?
A: 三板斧:
- 箭头函数(自动绑定)➡️
- bind方法(强制绑定)📌
- that=this(经典土办法)👴
📚 小结
通过本次学习,我们掌握了:
- 闭包的七十二变:从记忆函数到私有变量,闭包就像JavaScript的瑞士军刀
- 防抖节流大作战:学会用快递员等电梯和打工人摸鱼的类比理解这两个高频优化技巧
- 类封装的艺术:用家庭账本管理系统案例理解面向对象编程的核心思想
记住:前端开发就像打游戏,遇到性能瓶颈不要慌,拿出防抖节流的大招,优雅地解决卡顿问题!🎮
技术写作就像写情书,既要专业严谨,又要让人看得懂 ❤️
—— 一位摸鱼的前端工程师