在前端开发中,我们经常看到变量名以下划线开头,比如 _title
或 _count
,这是一种常见的编码约定,用来表示这个变量是"受保护的",不应该被外部直接访问。
但你有没有想过:
这些"下划线变量"真的是私有的吗?它们真的不能被修改吗?
本文将带你一步步揭开 JavaScript 中"伪私有"与"真私有"的面纱,深入理解变量作用域、闭包机制以及如何实现更安全的数据封装。
🧠 JavaScript 为什么没有"真正的私有"?
JavaScript 在设计之初并没有像 Java、C++ 那样的 private
关键字来限制类成员的访问权限。直到 ES6 引入了类(class)语法,才支持了使用 #
前缀定义真正的私有字段(如 #title
),但在那之前,开发者只能通过作用域 和闭包来模拟私有性。
这就导致了一个现象:很多开发者误以为以下划线开头的变量就是"私有变量",但实际上它只是个"伪私有"。
🔍 什么是"伪私有变量"?
来看一个典型的构造函数写法:
javascript
function Book(title) {
const _title = title;
this.getTitle = function () {
return _title;
};
}
在这个例子中:
_title
是用const
声明的局部变量,没有挂到this
上。- 外部无法通过
book._title
访问。 - 看起来像是"私有变量",但它真的不可见或不可改吗?
⚠️ 实际情况:
- 如果你在控制台打印出
book
对象,可能会在闭包中看到_title
的值。 - 在某些调试器中,甚至可以直接修改它的值。
- 所以,这种变量并不是完全私有的 ------ 它只是一个"受保护的命名约定"。
🔒 那什么才是"真正的私有变量"?
真正的私有变量应该满足两个条件:
- 外部无法直接访问
- 只能通过特定的方法间接操作
要实现这一点,我们需要借助 闭包(Closure)。
看下面这段代码:
javascript
function Book(title) {
let count = 0; // ✅ 真正的私有变量
const _title = title;
this.getTitle = function () {
return _title;
};
this.increaseCount = function () {
count++;
};
this.getCount = function () {
return count;
};
}
在这个例子中:
count
是用let
声明的局部变量,没有暴露给外部。- 外部既不能访问也不能修改
count
,除非调用公开方法(如increaseCount()
和getCount()
)。 - 这种方式利用了闭包的特性,确保变量只存在于内部上下文中,从而实现了"真正的私有性"。
📌 类比理解:办公室里的抽屉和保险柜
写法 | 类比 | 安全性 |
---|---|---|
this.title = title |
文件堆在办公桌上 | ❌ 完全不安全 |
const _title = title |
文件锁在抽屉里,钥匙你自己拿着 | ⚠️ 可能被撬开 |
let count = 0 |
文件锁在保险柜里,只有你有钥匙 | ✅ 完全安全 |
🤔 为什么推荐使用闭包来封装数据?
闭包之所以强大,是因为它可以:
- 创建独立的作用域空间
- 保持变量的生命周期
- 实现对外隐藏、对内开放的封装效果
这正是现代模块化开发中推崇的设计理念之一。
例如,我们可以这样封装一个计数器模块:
javascript
function createCounter() {
let count = 0;
return {
increment() {
count++;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
console.log(counter.count); // 输出 undefined
在这个例子中,外部根本无法访问 count
,只能通过返回的对象方法进行操作 ------ 这就是真正的封装。
💡 Demo一下:"伪私有"和"真私有"的区别
运行以下代码看看输出结果:
javascript
function Book() {
const _name = "JavaScript";
let count = 0;
this.getName = function () {
return _name;
};
this.inc = function () {
count++;
};
this.getCount = function () {
return count;
};
}
const b = new Book();
console.log(b._name); // undefined
console.log(b.count); // undefined
console.log(b.getName()); // JavaScript
b.inc();
console.log(b.getCount()); // 1
虽然 _name
和 count
都不在 this
上,但通过方法仍然可以访问和操作。这说明:
_name
是"伪私有"count
是"真私有"
🛠 如何判断一个变量是否真正私有?
你可以从以下几个方面判断:
判断标准 | 描述 |
---|---|
是否可以通过对象属性访问 | 不能访问则可能是私有 |
是否可以在控制台查看 | 如果看不到,则更接近私有 |
是否可以通过闭包操作 | 如果只能通过方法修改,则是真正的私有 |
是否属于闭包变量 | 属于闭包的变量更安全 |
JavaScript 中没有"真正的私有"关键字,但你可以通过闭包来模拟私有性。
_title
是一种"受保护的变量",适合封装;而let count = 0
才是真正的私有变量,外部完全看不见。
📘 推荐阅读&建议学习
如果你希望进一步掌握闭包、作用域链等进,建议继续学习:
- 《你不知道的 JavaScript》系列(上卷、中卷)
- MDN 文档中的 闭包
- 使用 IIFE(立即执行函数表达式)封装模块
- ES6+ 中的私有类字段(
#field
)