🧠 什么是闭包?
✍️ 作者:桑榆
🕓 更新时间:2025-11-06 🧠 关键词:闭包、作用域、变量提升
🧩 前言
最近在开发前端的组件库,创建了一个函数,这个函数的主要作用是创建动态的class。 实现统一的 BEM 风格 class 管理
这个函数如下所示:
typescript
type BEMType = string | [string, 'B' | 'E' | 'M' | undefined];
export function useClassnames(componentName: string) {
const prefix = 'sy';
const componentClass = ${prefix}-${componentName};
const c = (...arg: BEMType[]) => {
// return ${componentClass}-${suffix};
if (arg.length)
return arg.reduce((prev, cur) => {
if (Array.isArray(cur)) {
const arg1 = cur[0];
const arg2 = cur[1];
if (arg2 === 'E') {
return ${prev}__${arg1};
} else if (arg2 === 'M') {
return ${prev}--${arg1};
}
}
return ${prev}-${cur};
}, componentClass) as string;
return componentClass;
};
const ce = (e: string) => [e, 'E'] as BEMType;
const cm = (e: string) => [e, 'M'] as BEMType;
const cx = (cls: () => Record<string, boolean>) => {
return computed(() => classNames(cls()));
};
return {
c,
cx,
ce,
cm,
};
}这个函数就引出了闭包的概念。 ✅ 什么是闭包?
闭包就是:
内部函数可以访问外部函数作用域中的变量,即使外部函数已经返回。 这里 useClassnames 调用后,返回的函数(c, cx, ce, cm)仍然可以访问:
- prefix
- componentClass
- componentName
这些变量本应该在 useClassnames() 执行结束后失效,但因为闭包,它们仍然存在。
🎯 闭包在代码中的哪里?
👇 这是你代码的关键部分:
typescript
export function useClassnames(componentName: string) {
const prefix = 'sy';
const componentClass = `${prefix}-${componentName}`;
const c = (...arg: BEMType[]) => { ... }
const ce = (e: string) => [e, 'E'] as BEMType;
const cm = (e: string) => [e, 'M'] as BEMType;
const cx = (cls: () => Record<string, boolean>) => {
return computed(() => classNames(cls()));
};
return { c, cx, ce, cm };
}c, ce, cm, cx 这 4 个函数都依赖于:
text
prefix
componentClass
componentName而这些变量 属于 useClassnames 的内部作用域。
当 useClassnames() 执行完毕时,按常规 JS 行为,这些变量应该“销毁”。
但因为你返回了这几个函数,并且这些函数 引用了外部作用域中的变量。
➡️ 所以它们形成闭包
➡️ prefix 和 componentClass 会一直保存在内存中
➡️ 直到组件卸载
这就是闭包。
🌟 闭包在这里有什么用?
1. 保存组件前缀,使 class 生成更方便
我只用写:
ts
const { c, ce, cm } = useClassnames("button");之后:
text
c() → sy-button
c(ce("icon")) → sy-button__icon
c(cm("primary")) → sy-button--primary
c("size", "large") → sy-button-size-large不用每次重复写前缀,因为通过闭包已经记住:
text
componentClass = "sy-button"2. 保持 BEM 工具函数天然“记住”组件名称
非常适合组件库设计(和 Element UI、AntD 类似)
3. 多处使用,但每个组件拥有自己的 namespace
每次调用 useClassnames,会创建一个“私有闭包环境”。
调用 1:
ts
useClassnames("menu")调用 2:
ts
useClassnames("button")两者互不影响,因为有两个不同闭包环境。
