🔥 为啥写这个?
最近疯狂背八股,闭包概念懂了,但一到实际开发就懵... 昨天写组件终于悟了!分享给同被折磨的友友~
一、闭包是啥?
简单说就是:函数嵌套时,内层函数能记住外层函数的变量,哪怕外层执行完了。
举个🌰:写一个延迟计数器(点击按钮,1 秒后数字 +1 ),用闭包实现:
js
function createCounter() {
let count = 0;
return () => {
setTimeout(() => {
count++; // 内层函数"记住"count,每次调用持续累加 console.log(count);
}, 1000);
};
}
const add = createCounter();
add(); // 1秒后输出 1
add(); // 1秒后输出 2(闭包保留了 count=1 的状态)
二、实际开发踩坑:数据更新不生效
场景:延迟更新页面数据
假设做一个按钮,点击后 1 秒更新页面显示的数字,直接用闭包会遇到 "数据改了,但页面没变化" 的问题:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>闭包踩坑示例</title>
</head>
<body>
<button id="btn">延迟+1</button>
<div id="result">count: 0</div>
<script>
let count = 0;
const btn = document.getElementById('btn');
const result = document.getElementById('result');
btn.addEventListener('click', () => {
setTimeout(() => {
count++;
result.innerText = `count: ${count}`; // 手动更新 DOM!
}, 1000);
});
</script>
</body>
</html>
为啥坑?
- 纯 JS 开发时,普通变量修改后不会自动更新 DOM ,必须手动操作
innerText
/innerHTML
。 - 如果用框架(如 Vue/React),不配合响应式语法,也会遇到类似问题!
三、正确姿势:结合开发场景适配
方案 1:手动更新 DOM(纯 JS 开发)
html
<script>
let count = 0;
const btn = document.getElementById('btn');
const result = document.getElementById('result');
btn.addEventListener('click', () => {
setTimeout(() => {
count++;
// 关键:手动更新 DOM,让页面变化
result.innerText = `count: ${count}`;
}, 1000);
});
</script>
核心逻辑 :JS 不自动监听变量变化,修改数据后必须手动操作 DOM 才能更新页面。
方案 2:配合框架响应式(以 React 为例)
React 中,用 useState
管理状态,闭包捕获 setState
来更新:
jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
// 用 setCount 触发响应式更新
setCount(prev => prev + 1);
}, 1000);
};
return (
<div>
<button onClick={handleClick}>延迟+1</button>
<div>count: {count}</div>
</div>
);
}
避坑点:
- 直接修改
count
(如count++
)不会触发更新,必须用setState
。 - 闭包若捕获旧的
count
值,会导致 "状态不更新"(俗称stale closure
问题),用函数式更新(prev => prev + 1
)可解决。
四、闭包必用场景(开发高频需求)
场景 1:搜索框防抖(避免频繁请求)
js
// 用闭包保存定时器 ID,防止重复触发
const debounce = (fn, delay) => {
let timer = null; // 闭包变量,多次调用不会重置
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
};
};
// 实际用:监听输入框变化
const input = document.getElementById('search-input');
input.addEventListener('input', debounce((e) => {
console.log('发请求搜索:', e.target.value);
}, 500));
踩坑点:如果不用闭包,每次输入都会重置定时器,防抖功能直接失效!
场景 2:模块私有变量(避免全局污染)
js
// utils/counter.js
const createCounter = () => {
let count = 0; // 闭包变量,外部无法访问
return {
increment: () => count++,
getCount: () => count
};
};
// 其他文件引入
import { createCounter } from './utils/counter.js';
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1(外部无法直接改 count)
价值:做工具库 / 组件时,用闭包实现 "私有状态",防止全局变量污染。
五、避坑总结:闭包开发关键提醒
开发场景 | 正确姿势 | 常见错误 |
---|---|---|
纯 JS 开发 | 修改数据后手动更新 DOM | 只改变量,页面不变化 |
React 开发 | 用 useState / 函数式更新 |
直接修改状态,不触发更新 |
工具库开发 | 用闭包封装私有变量 | 全局变量污染逻辑 |
六、总结:闭包该咋用?
✅ 场景:需要 "记住" 变量状态时(比如防抖函数、组件私有变量)
❌ 踩坑:和 React 响应式结合时,注意用useState包裹
💡 小技巧:写闭包后,检查变量是 "值传递" 还是 "引用传递"
最后碎碎念
背八股真的好痛苦... 但把知识点砸到项目里,突然就通了! 你们遇到过这些坑吗?
- 用闭包写防抖,第一次点击没生效?(定时器逻辑没处理好!)
- React 里遇到 "陈旧闭包",状态永远是旧的?
- 有没有不用闭包也能实现类似功能的奇技淫巧?