同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~
(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)
你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?
你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?
就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。
一天只有24小时,时间永远不够用,常常感到力不从心。
技术行业,本就是逆水行舟,不进则退。
如果你也有同样的困扰,别慌。
从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲。
这一次,我们一起慢慢来,扎扎实实变强。
不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,
咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。
写 JavaScript 时,你有没有遇到过这种困惑:明明对象有这个属性,用 Object.keys() 却拿不到?用 for...in 遍历也看不到?这不是 bug,而是 可枚举性(enumerable) 在起作用。
enumerable音标:/ɪˈnjuːm(ə)rəb(ə)l/
这篇文章不讲晦涩的底层,只讲三件事:可枚举性是什么、怎么用、坑在哪。适合:刚开始学 JS 的、已经会写但概念模糊的、以及想系统掌握对象遍历的前端。
一、先搞清楚:可枚举性到底是什么?
1.1 一句话说清楚
把 JavaScript 对象想象成一个抽屉,属性就是抽屉里的文件:
- 可枚举:贴了「可展示」标签 → 别人翻抽屉(遍历)时能看到;
- 不可枚举:没有「可展示」标签 → 别人翻抽屉看不到,但你自己知道在哪,能直接拿起来用。
可枚举性的唯一作用:决定属性能不能被「遍历工具」看到,和属性本身是否存在、能不能直接用无关。
1.2 用极简代码看懂区别
// 1. 创建对象,放一个可枚举属性(默认就是可枚举)
const drawer = {
fileA: "购物清单" // 默认带「可展示」标签
};
// 2. 添加一个不可枚举属性
Object.defineProperty(drawer, "fileB", {
value: "私密日记",
enumerable: false // 关键:设为不可枚举
});
// 3. 遍历时能看到什么?
console.log("遍历看到的:", Object.keys(drawer));
// ['fileA'] —— 只有 fileA
// 4. 直接访问呢?
console.log("直接访问 fileA:", drawer.fileA); // 购物清单
console.log("直接访问 fileB:", drawer.fileB); // 私密日记 —— 照样能用到
结论:不可枚举的属性只是「隐身」,不是「消失」—— 遍历看不到,但能直接访问和使用。
二、哪些「遍历工具」会受可枚举性影响?
2.1 对比一览表
| 方法 / 写法 | 是否受可枚举性影响 | 说明 |
|---|---|---|
Object.keys() | ✅ 是 | 只返回可枚举的自身属性 |
for...in | ✅ 是 | 只遍历可枚举属性(含原型链) |
Object.values() | ✅ 是 | 只处理可枚举的自身属性 |
Object.entries() | ✅ 是 | 只处理可枚举的自身属性 |
Object.getOwnPropertyNames() | ❌ 否 | 返回所有自身属性(含不可枚举) |
Object.assign()讲解 | ✅ 是 | 只复制可枚举属性 |
展开运算符 {...obj} | ✅ 是 | 只展开可枚举属性 |
JSON.stringify() | ✅ 是 | 只序列化可枚举属性 |
2.2 代码演示
const user = {
name: "小明",
age: 18
};
Object.defineProperty(user, "idCard", {
value: "110xxxxxxxx",
enumerable: false
});
// 受可枚举性影响 —— 看不到 idCard
console.log(Object.keys(user)); // ['name', 'age']
console.log(Object.entries(user)); // [['name','小明'], ['age',18]]
console.log(JSON.stringify(user)); // {"name":"小明","age":18}
// 不受影响 —— 能拿到 idCard
console.log(Object.getOwnPropertyNames(user)); // ['name','age','idCard']
// 直接访问 —— 都能用
console.log(user.idCard); // 110xxxxxxxx
三、日常写代码:什么时候用不可枚举?
3.1 推荐原则
- 希望「隐藏」但不删除:比如身份证、密码、内部标记等,不想被遍历、序列化、复制出去时,设为
enumerable: false。 - 不希望参与常规拷贝:例如某些内部元数据,不希望被
Object.assign、展开运算符、JSON.stringify带走时。 - 模仿内置对象:像数组的
length等不可枚举属性,自定义类时也可用不可枚举来模拟。
3.2 常见场景示例
const user = {
nickname: "小明", // 公开
avatar: "https://xxx.jpg"
};
// 私密信息设为不可枚举
Object.defineProperty(user, "idCard", {
value: "110xxxxxxxx",
enumerable: false
});
// 展示用户卡片:遍历只得到公开信息,不会泄露身份证
console.log("用户卡片:", Object.keys(user)); // ['nickname', 'avatar']
// 后台校验:直接访问仍能拿到身份证
if (user.idCard) {
console.log("身份校验通过");
}
四、常见坑:容易踩在哪?
4.1 坑一:用 Object.assign 或展开运算符「复制不全」
const source = { a: 1, b: 2 };
Object.defineProperty(source, "c", {
value: 3,
enumerable: false
});
const copy1 = Object.assign({}, source);
const copy2 = { ...source };
console.log(copy1); // { a: 1, b: 2 } —— 没有 c
console.log(copy2); // { a: 1, b: 2 } —— 没有 c
不可枚举属性不会被复制。如果需要完整拷贝(含不可枚举),要用其他方式(如 Object.getOwnPropertyNames + 循环等)。
4.2 坑二:JSON.stringify 不会序列化不可枚举属性
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
Object.defineProperty(config, "secretKey", {
value: "sk-xxxx",
enumerable: false
});
console.log(JSON.stringify(config));
// {"apiUrl":"https://api.example.com","timeout":5000}
// secretKey 不会出现 —— 这是好事,避免泄露;但有时也会忘
如果需要把不可枚举属性也存到 JSON,要手动处理。
4.3 坑三:以为不可枚举 = 不能访问
不可枚举只影响「遍历工具」,不影响直接访问:
const obj = {};
Object.defineProperty(obj, "hidden", {
value: "我在这里",
enumerable: false
});
console.log(Object.keys(obj)); // []
console.log(obj.hidden); // "我在这里" —— 照常用
4.4 坑四:默认属性的可枚举性不同
通过字面量或 obj.x = 1 添加的属性,默认是可枚举的;
通过 Object.defineProperty 添加时,如果不写 enumerable,默认是不可枚举:
const obj = {};
Object.defineProperty(obj, "x", { value: 1 });
// 没有写 enumerable,默认 enumerable: false
console.log(Object.keys(obj)); // [] —— x 不可枚举
需要可枚举时,要显式写 enumerable: true。
五、和「可枚举性」相关的两个小点
5.1 如何查看属性的可枚举性?
用 Object.getOwnPropertyDescriptor:
const obj = { a: 1 };
Object.defineProperty(obj, "b", { value: 2, enumerable: false });
console.log(Object.getOwnPropertyDescriptor(obj, "a"));
// { value: 1, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(obj, "b"));
// { value: 2, writable: false, enumerable: false, configurable: false }
5.2 内置对象里也有不可枚举属性
数组的 length、对象的 toString、constructor 等很多内置属性是不可枚举的:
console.log(Object.keys([1, 2, 3])); // ['0', '1', '2'] —— 没有 length
console.log([1, 2, 3].length); // 3 —— 但能直接访问
六、总结:一张表 + 一句话
| 要点 | 说明 |
|---|---|
| 唯一作用 | 决定遍历工具能不能「看到」这个属性 |
| 不可枚举 ≠ 消失 | 遍历看不到,但能直接访问 |
| Object.keys / for...in | 只看可枚举属性 |
| Object.getOwnPropertyNames | 包含不可枚举属性 |
| assign / 展开 / JSON.stringify | 只处理可枚举属性 |
| 适用场景 | 私密信息、内部标记、不想被复制/序列化的属性 |
一句话:可枚举性就是给属性贴「可展示」标签,遍历工具看标签,直接访问不看标签。记住这两点,就能少踩坑,对象遍历也更清晰。
学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。
后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。
关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。
如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。
我是 Eugene,你的电子学友,我们下一篇干货见~