JS怎么获取普通对象的属性个数

作者:小括号

时间:2026-10-30

浏览:423

本文详细解答在JavaScript中如何获取普通对象的属性个数。从最常用的Object.keys(obj).length展开,深入探讨不可枚举属性、Symbol键以及原型链干扰的处理方式,并提供完整的流程图和API选型建议。

文章目录

假设你正在处理接口返回的用户配置对象 const userConfig = { theme: "dark", autoSave: true, fontSize: 14 },想要判断该对象中是否已经配置了属性,或者校验提交表单的字段数量。很多人下意识会尝试写出 userConfig.length,但控制台却输出了 undefined。在 JavaScript 中,普通对象(Plain Object)本身并不内置维护长度信息的 length 或 size 属性。要在实际业务中准确获取普通对象的属性个数,最标准、也是 95% 以上开发场景下的首选写法是:Object.keys(obj).length。

JS怎么获取普通对象的属性个数
图 0:普通对象属性长度求解路径概览

一、为什么不能直接通过 obj.length 获取?

在深入具体方法前,需要厘清 JavaScript 内部的数据结构机制。对于数组(Array)而言,引擎底层维护了一个特殊的自动更新属性 length,每次调用 push()、pop() 或通过数字下标修改数组元素时,数组长度都会实时联动。同样,ES6 引入的 Map 与 Set 也是专职用于集合操作的类,提供了原生的 size 访问器属性。

然而普通对象(通过 {} 字面量或 new Object() 创建的对象)被设计为键值对哈希映射字典。对象的键名是离散且无序的字符串或 Symbol,引擎没有理由为每个对象实时维护一个可能带来额外开销的计数器。因此,读取 obj.length 只会去查找对象上名为 "length" 的属性,如果对象未曾显式定义该字段,自然返回 undefined。

const user = { name: "Alice", age: 25 };

// 示例输出:undefined(普通对象无原生 length 属性)
console.log(user.length);

// 正确做法:先提取属性键名数组,再求其长度
console.log(Object.keys(user).length); // 示例输出:2

二、标准常用解法:Object.keys(obj).length

Object.keys() 是 ECMAScript 5 就已标准化的静态方法,它接收目标对象作为参数,返回一个包含该对象自身所有可枚举(enumerable)字符串属性键名的数组。配合数组自带的 .length 属性,便能直接得出属性个数。

代码示例与常见操作验证

// 1. 空对象
const emptyObj = {};
console.log(Object.keys(emptyObj).length); // 示例输出:0

// 2. 常规数据对象
const product = {
  id: "P-1002",
  title: "无线键盘",
  price: 199,
  inStock: true
};
console.log(Object.keys(product).length); // 示例输出:4

// 3. 动态删除属性后
delete product.inStock;
console.log(Object.keys(product).length); // 示例输出:3

Object.keys(obj).length 成为工业界标准方案的核心原因在于其严格的边界控制:

  • 仅统计自身属性:不会沿着原型链(Prototype Chain)向上查找,天然隔离继承属性。
  • 仅统计可枚举属性:自动过滤 JavaScript 内部机制注入的不可枚举字段。
  • 执行预期稳定:只要属性是通过常规字面量或赋值语句创建的,均能被 100% 准确计入。

三、包含特殊属性时的完整处理方案

尽管 Object.keys() 能够应对绝大多数业务需求,但在一些涉及底层框架开发、元编程或对象深度镜像的场景下,对象中可能会引入不可枚举属性或 Symbol 类型键名。此时需要根据目标属性的可见性范围选用更精确的方法。

JS怎么获取普通对象的属性个数
图 1:获取普通对象属性个数的决策路径与执行流程

1. 统计包含不可枚举属性的个数:Object.getOwnPropertyNames()

当通过 Object.defineProperty() 显式指定 enumerable: false 时,该属性虽然真实存在于对象上,但会被 Object.keys() 忽略。如果需要获取包含这类“隐藏属性”在内的所有字符串键个数,应使用 Object.getOwnPropertyNames()。

const systemConfig = {
  mode: "production"
};

// 定义一个不可枚举属性
Object.defineProperty(systemConfig, "secretKey", {
  value: "auth_token_9981",
  enumerable: false // 隐藏,不可枚举
});

console.log(Object.keys(systemConfig).length);                // 示例输出:1(忽略 secretKey)
console.log(Object.getOwnPropertyNames(systemConfig).length); // 示例输出:2(包含 secretKey)

2. 统计以 Symbol 作为键名的属性:Object.getOwnPropertySymbols()

ES6 引入的 Symbol 可以作为对象的唯一属性键。不论该 Symbol 属性是否可枚举,Object.keys() 和 Object.getOwnPropertyNames() 都会将其跳过。若要专门统计这类属性的个数,需调用 Object.getOwnPropertySymbols()。

const symId = Symbol("userId");
const symHash = Symbol("userHash");

const userProfile = {
  account: "developer",
  [symId]: 8801,
  [symHash]: "a8f7c9e"
};

console.log(Object.keys(userProfile).length);                  // 示例输出:1(仅 account)
console.log(Object.getOwnPropertySymbols(userProfile).length); // 示例输出:2(提取两个Symbol键)

3. 统计全部自有属性(全量统计):Reflect.ownKeys()

如果业务目标是统计对象自身拥有的绝对全量属性(包含可枚举与不可枚举字符串属性,以及全部 Symbol 键),ES6 提供的 Reflect.ownKeys(obj) 是最简洁、一步到位的标准 API。它相当于 Object.getOwnPropertyNames() 与 Object.getOwnPropertySymbols() 结果的并集。

const mixedObject = {
  regularProp: "normal"
};

Object.defineProperty(mixedObject, "hiddenProp", {
  value: 404,
  enumerable: false
});

mixedObject[Symbol("meta")] = "metadata";

// 获取绝对全部自身属性键数量
const totalCount = Reflect.ownKeys(mixedObject).length;
console.log(totalCount); // 示例输出:3
JS怎么获取普通对象的属性个数
图 2:JS 对象属性空间构成与各 API 提取范围对比

四、原型链属性的陷阱与边界防御

在处理对象属性计数时,初学者容易陷入两个经典陷阱:误用 for...in 循环导致原型链属性渗透,以及直接传入 null / undefined 造成运行时崩溃。

1. 为什么坚决不要用未过滤的 for...in 计数?

for...in 循环会递归沿着对象的原型链向上查找,枚举出所有原型上的可枚举属性。如果对象是通过特定原型继承构建的,或者运行环境中有旧库向 Object.prototype 扩展了公共方法,未过滤的循环会导致计数值出现严重偏差。

const parent = { inheritedAttr: "shared" };
const child = Object.create(parent);
child.ownAttr = "mine";

// 错误示范:使用 for...in 累计
let count = 0;
for (let key in child) {
  count++;
}
console.log(count); // 示例输出:2(把原型的 inheritedAttr 也算进去了!)

// 正确做法:直接使用 Object.keys
console.log(Object.keys(child).length); // 示例输出:1(安全隔离原型)
注意:如果历史代码因特定需求必须保留 for...in 循环,务必在循环体内部使用现代标准的 Object.hasOwn(obj, key)(ES2022)或 Object.prototype.hasOwnProperty.call(obj, key) 严格判断是否为自身属性。

2. 对空值与异常输入的防御性写法

在真实的业务链路中,待统计的对象往往来自函数传参或接口反序列化,其值可能是 null 或 undefined。如果直接执行 Object.keys(null),V8 引擎会立即抛出 TypeError: Cannot convert undefined or null to object,直接中断整个调用栈。

在工程实践中,推荐使用空值合并运算符(Nullish Coalescing Operator ??)进行防御性兜底:

// 高健壮性生产写法:安全返回 0 而不是抛出致命异常
function getObjectSize(target) {
  return Object.keys(target ?? {}).length;
}

console.log(getObjectSize({ a: 1, b: 2 })); // 示例输出:2
console.log(getObjectSize(null));              // 示例输出:0
console.log(getObjectSize(undefined));         // 示例输出:0
JS怎么获取普通对象的属性个数
图 3:常见边界陷阱与高健壮性安全防御方案

五、性能考量与高频场景下的数据结构选型

理解 Object.keys(obj).length 的底层成本对于高性能系统尤为关键。该方法的运行流程分为两步:

  1. 引擎遍历对象并收集所有的自有键,在内存堆区新分配一个字符串数组;
  2. 读取该临时数组的 length 属性,随后该临时数组等待垃圾回收(GC)。

这种机制意味着时间复杂度为 $O(N)$,且伴随一次内存分配。对于普通业务对象(十几个或几十个属性),这种开销微乎其微。但在以下场景下需要警惕:

  • 在 requestAnimationFrame 动画帧或每秒执行上万次的高频函数内调用;
  • 对象内存储了数万条以上键值对的超大型字典缓存。

替代方案:重构为 ES6 Map

如果业务架构需要频繁执行键值对添加、删除以及实时读取容量大小,应当使用原生 Map 结构替代普通对象。Map 内部维护了高效的哈希桶与尺寸计数器,其 map.size 属性的读取时间复杂度为恒定的 $O(1)$,完全不需要分配临时数组。

// 普通对象高频计数:每次都有 O(N) 遍历及中间数组创建
const cacheObj = {};
// ... 大量写入
const sizeA = Object.keys(cacheObj).length;

// Map 结构:O(1) 原生读取,零额外内存开销
const cacheMap = new Map();
cacheMap.set("item1", 100);
cacheMap.set("item2", 200);
console.log(cacheMap.size); // 示例输出:2(常数级耗时)
JS怎么获取普通对象的属性个数
图 4:获取对象属性个数核心方案综合对比总结

六、方案对比与选型总结

为了便于在日常编码与代码审查中快速决策,以下将各种获取属性个数的方法及其特性梳理成表:

方法表达式 提取属性范围 不可枚举属性 Symbol 键 原型链属性 推荐使用场景
Object.keys(obj).length 自身可枚举字符串键 排除 排除 排除 95% 的日常业务场景首选,结果最符合直觉
Object.getOwnPropertyNames(obj).length 自身所有字符串键 包含 排除 排除 排查内部隐藏配置、框架对象属性完整性检测
Object.getOwnPropertySymbols(obj).length 自身所有 Symbol 键 包含 专职统计 排除 模块私有元数据标记统计、符号键专用计数
Reflect.ownKeys(obj).length 自身绝对全部属性 包含 包含 排除 对象完整序列化、深拷贝属性对齐、底层代理开发
map.size Map 集合的全部键值对 不适用 天然支持 无污染 需高频执行增删与容量查询的大数据量字典场景
核心记忆口诀:
绝大多数情况直接写 Object.keys(obj).length;若担心空指针报错,加上合并符写成 Object.keys(obj ?? {}).length;若对象使用了 Symbol 或隐藏属性,换用 Reflect.ownKeys(obj).length;高频海量字典计数,换用 Map.prototype.size。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章