枚举不理解?一文让你醍醐灌顶

64 阅读6分钟

同学们好,我是 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 推荐原则

  1. 希望「隐藏」但不删除:比如身份证、密码、内部标记等,不想被遍历、序列化、复制出去时,设为 enumerable: false
  2. 不希望参与常规拷贝:例如某些内部元数据,不希望被 Object.assign、展开运算符、JSON.stringify 带走时。
  3. 模仿内置对象:像数组的 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、对象的 toStringconstructor 等很多内置属性是不可枚举的:

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,你的电子学友,我们下一篇干货见~