Skip to content

🧠 什么是闭包?

✍️ 作者:桑榆
🕓 更新时间: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")

两者互不影响,因为有两个不同闭包环境。