前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库闭包实现私有变量 模块模式
JavaScript函数与作用域

怎么用闭包实现私有变量?有哪几种写法?

把变量声明在一个函数内部,函数返回几个能读写这个变量的方法。函数执行完了,返回的方法还引用着这个变量,所以它不会被回收;外面又没有任何办法直接拿到它,只能调用这些方法,这就实现了私有。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

闭包私有变量的三个要素

  1. 局部变量声明在工厂函数里,不挂到返回的对象上,外部没有引用能碰到它
  2. 特权方法返回的方法和变量在同一个作用域里定义,能读写它
  3. 独立副本每次调用工厂函数都会新建一个作用域,各个实例的变量互不影响

私有只是「代码里访问不到」,不是加密。方法把值返回出去,外面照样能拿到,浏览器调试工具里也能看到闭包里的变量。

核心回答

先记住这个答案

做法是写一个函数,把要保护的变量声明在函数里,然后返回一个对象,对象上只放几个能读写这个变量的方法。函数执行完之后,因为返回的方法还引用着这个变量,它不会被回收,这就是闭包。外面的代码拿到的只是方法,没有任何办法直接读或改这个变量,只能按方法规定的方式操作。常见的写法有两种:可以多次调用、每次产生一个新实例的工厂函数,和只执行一次、得到一个单例的模块模式(用 IIFE 包起来)。

  • 变量放在函数里,只把操作它的方法返回出去
  • 每调用一次工厂函数,就有一份独立的私有变量
  • 下划线开头的属性只是约定,不是真私有
  • 用 class 时优先考虑 # 私有字段

工厂函数:每次调用一份新状态

最常用的写法,一个计数器就能说明所有要点。

用工厂函数封装计数器JavaScript
function createCounter(initial = 0) {
  let count = initial // 私有变量,不挂到返回的对象上
  return {
    increment() {
      count += 1
      return count
    },
    value() {
      return count
    }
  }
}

const a = createCounter()
const b = createCounter(10)
a.increment()
a.increment()
console.log(a.value(), b.value()) // 两个实例互不影响
console.log(a.count) // 对象上没有 count
a.count = 100 // 只是给对象加了个新属性
console.log(a.value())
查看输出与解释
2 10
undefined
2

a 和 b 是两次调用得到的,每次调用都新建了一个作用域,里面各有一个 count,所以 a 加到 2,b 还是 10。 最后两行很关键:a.count = 100 没有报错,但它只是在返回的对象上新加了一个叫 count 的属性,和闭包里的那个 count 毫无关系,a.value() 还是 2。私有变量根本不是对象的属性,所以从对象上既读不到,也改不了。

模块模式:只需要一份时用 IIFE

如果整个应用只需要一份状态,比如一个全局配置、一个缓存,就不用工厂函数,直接用 IIFE 执行一次,把返回的对象存起来。写法是 const store = (function () { let data = {}; return { get(k) {...}, set(k, v) {...} } })()。原理和工厂函数一样,只是函数只调用一次。

在 ES 模块里,这件事更简单:模块本身就是一个独立作用域,模块顶层声明的变量只要不 export,外部就访问不到,效果和模块模式一样,不需要再包 IIFE。

和其它「私有」写法对比

JavaScript 里说「私有」,面试官一般想听你把下面几种写法比较一下。

下划线约定、# 私有字段、WeakMap
下划线开头的属性,比如 this._count,只是团队约定,外面照样能读能改,没有任何保护。 class 里的 #count 是语言层面的私有字段,类外面写 obj.#count 直接是语法错误。它的好处是方法定义在原型上,所有实例共用,不像闭包那样每个实例都要创建一套新方法。写 class 时优先用它。 WeakMap 是以前没有 # 时的方案,用实例作为 key 存私有数据,实例被回收后数据也自动释放。现在有了 # 私有字段,这种写法用得少了。
回答前,多想一步

容易答错的地方

以为 Object.freeze 能保护闭包里的变量
Object.freeze 冻结的是返回的对象,防止别人改掉或删掉上面的方法。闭包里的变量不在对象上,它本来就改不到,方法内部也照样能改它。两者保护的是不同的东西。
以为闭包私有变量没有代价
每调用一次工厂函数,返回的方法都会重新创建一份。创建成千上万个实例时,内存会比把方法放在原型上的 class 多。另外,闭包里如果引用了大对象,只要方法还活着,大对象就回收不了。
试着用自己的话回答

面试官还会怎么问?

返回的方法解构出来单独用,还能正常工作吗?

能。const { increment } = createCounter() 之后直接调用 increment() 没问题,因为方法里用的是闭包变量 count,没有用 this。这也是闭包写法比 class 方便的地方,class 的方法解构出来 this 就丢了。

多个实例想共享一个私有变量怎么办?

把变量放到工厂函数外面一层,比如模块顶层或者再包一层 IIFE,所有实例的方法都引用同一个变量。比如记录一共创建了多少个实例,就可以这么做。

React 的 useState 和这里的闭包有关系吗?

有。组件每次渲染都是一次函数调用,事件处理函数通过闭包读到的是那一次渲染时的 state。定时器或异步回调里读到旧值的「闭包陷阱」,就是回调记住的是旧那次渲染的变量。

从一道题,走向一组知识

把知识连起来

函数与作用域

什么是副作用?纯函数有什么特点,为什么要用它?

闭包里的私有变量是一份被方法修改的状态,读写它的方法都不是纯函数。

函数与作用域

函数默认参数里写 x = x,为什么会报错?

工厂函数经常用默认参数给私有变量设初始值,默认参数的求值顺序和暂时性死区规则要一起弄清。

参考资料

  • JavaScript:函数与作用域参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 工厂函数:每次调用一份新状态
  3. 模块模式:只需要一份时用 IIFE
  4. 和其它「私有」写法对比
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑