JS获取对象长度时是否包含继承属性

作者:代码拾荒

时间:2026-10-30

浏览:278

深入解答在JavaScript中获取对象长度时是否包含继承属性的问题。详细解析Object.keys、for...in、Reflect.ownKeys在原型链继承属性上的统计表现,提供清晰判断逻辑、对比矩阵与实战代码。

文章目录
核心答案:在 JavaScript 中获取普通对象的“长度”(即属性键的数量)时,绝大多数常规方法(如 Object.keys(obj).length)默认不包含任何原型链上的继承属性。普通对象本身没有内置的 .length 属性,通常依靠反射 API 来统计键数。这些 API 严格限定在对象本身的“自有属性(Own Properties)”范围内;只有当你显式使用 for...in 循环遍历计数,或者手动递归访问原型链时,继承属性才会被计入。
JS获取对象长度时是否包含继承属性
题图:JavaScript 实例自有属性与原型链继承属性的统计隔离表现

一、JavaScript 中的“对象长度”到底指什么?

在初学 JavaScript 时,很多开发者习惯了使用 array.length 或 string.length 来获取数据长度。然而,对于普通对象(如 const obj = { a: 1, b: 2 }),其本身并不存在原生内置的 length 属性(如果直接访问 obj.length,得到的结果是 undefined)。

因此,日常开发中所说的“获取对象长度”,本质上是统计该对象所拥有的属性键(Keys)的数量。目前业界最标准、使用最广泛的统计方式是:

// 最常用的对象“长度”统计写法
const size = Object.keys(obj).length;

那么核心问题来了:如果这个对象继承自另一个原型对象,原型上的属性会不会被 Object.keys(obj).length 计算在内?让我们直接通过一段带继承关系的代码进行验证。

二、标准场景验证:Object.keys 与原型继承

我们使用 Object.create() 创建一个带有清晰原型链继承关系的对象,并分别挂载自有属性与继承属性:

// 1. 定义原型对象(父级原型)
const animal = {
  type: 'creature',      // 继承属性
  breathe() {            // 继承方法(也是属性)
    return true;
  }
};

// 2. 基于 animal 创建实例对象 dog
const dog = Object.create(animal);

// 3. 为 dog 添加实例自身的“自有属性”
dog.name = '旺财';
dog.age = 3;

// 4. 验证直接属性读取(通过原型链能够正常读取到继承属性)
console.log(dog.name);    // 输出: '旺财' (自有属性)
console.log(dog.type);    // 输出: 'creature' (沿原型链查找命中继承属性)

// 5. 统计 dog 的对象长度
console.log(Object.keys(dog));         // 输出: ['name', 'age']
console.log(Object.keys(dog).length);  // 输出: 2

从上述执行结果可以清晰看到:虽然 dog.type 能够正常读取出值,但 Object.keys(dog) 返回的数组中只包含了 ['name', 'age'] 两个自有属性,数组长度为 2。原型链上的 type 与 breathe 并没有被计入。

这充分证明:使用 Object.keys() 计算对象长度时,绝不会包含继承属性。

三、原型链分层:为什么属性可以读到却不计入长度?

很多开发者之所以会对这个问题产生疑惑,是因为混淆了 JavaScript 的“属性访问机制([[Get]])”与“键名枚举机制([[OwnPropertyKeys]])”。

当你书写 dog.type 时,JavaScript 引擎会启动原型链查找(Prototype Chain Lookup):先检查 dog 实例自身是否有该属性;如果没有,就顺着内部指针 [[Prototype]] 到其原型 animal 上查找,直到顶层的 Object.prototype。这种机制让原型属性在“只读使用”时感觉如同对象自身的一部分。

然而,在统计属性键时,Object.keys() 调用的是底层的自有键检索规范。规范明确规定该方法仅检查目标对象自身的槽位,不会向上攀爬原型链。二者的边界如下图所示:

JS获取对象长度时是否包含继承属性
图1:JavaScript 原型链结构与属性统计边界分层示意图

四、四种主流统计方式的继承属性表现对比

在 JavaScript 中,除了 Object.keys() 外,开发者有时还会接触到其他获取属性集合或遍历属性的方式。不同的方法对“继承属性”以及“不可枚举属性”、“Symbol 属性”的处理策略截然不同:

1. Object.keys(obj).length(标准首选)

仅返回对象自身所拥有的、可枚举的字符串属性。不包含原型链继承属性,不包含不可枚举属性,也不包含 Symbol 类型的键。这是现代 JavaScript 中最安全、最符合直觉的长度统计手段。

2. for...in 循环累加(唯一默认穿透继承属性的方法)

for...in 是一种历史悠久的遍历语法,其设计初衷就是为了遍历对象自身及其原型链上的所有可枚举属性。如果使用 for...in 搭配计数器累加对象长度,继承属性就会被自动算入:

// 使用 for...in 累加统计 dog 对象的属性数量
let count = 0;
for (let key in dog) {
  count++;
}
console.log(count); // 输出: 4 (包含了 name, age 以及继承的 type, breathe)
注意:for...in 计数陷阱

在绝大多数业务逻辑中,将原型链上的方法和共享属性算进当前对象长度通常是非预期的错误。如果不小心在 for...in 中漏掉了过滤判断,就会导致对象长度虚高。如果必须使用 for...in 统计自有长度,必须使用 Object.hasOwn(obj, key) 进行拦截。

3. Object.getOwnPropertyNames(obj).length

返回对象自身的所有字符串属性,包括可枚举与不可枚举属性(例如通过 Object.defineProperty 设置了 enumerable: false 的属性),但同样不包含任何原型链上的继承属性。

4. Reflect.ownKeys(obj).length

属于 ES6 引入的反射 API,返回对象自身的全部键名,无论可枚举与否,并且同时支持字符串键和 Symbol 键。但请注意其名称中的 ownKeys(自有键)——它同样严格限制在对象本体,不穿透继承属性。

JS获取对象长度时是否包含继承属性
图2:常用属性获取与长度统计 API 检索维度对比矩阵

五、实战指南:如何根据业务诉求正确统计对象长度

面对实际开发任务,我们需要根据业务对“继承属性”、“Symbol 属性”的诉求,选择清晰、无副作用的实现方案。

JS获取对象长度时是否包含继承属性
图3:JavaScript 对象长度统计技术选型与实现流程图

场景 A:常规业务数据(绝大多数场景,不含继承属性)

在处理后端返回的 JSON 数据、前端表单状态或普通的键值对配置时,我们只需要统计实例自身的有效数据。直接使用 Object.keys(obj).length 即可,既高效又避免原型污染:

function getObjectLength(obj) {
  if (obj === null || typeof obj !== 'object') {
    return 0;
  }
  return Object.keys(obj).length;
}

场景 B:确实需要包含继承属性(含原型链合并统计)

如果业务场景确需计算一个对象所能访问到的全部有效属性总数(包括原型链继承的属性),不能直接将父子对象的 length 简单相加,因为子对象可能存在属性遮蔽(Shadowing)。

正确做法是借助 Set 收集所有可见键名进行去重:

// 包含继承属性的完整对象长度统计函数
function getAllPropertyCount(target) {
  if (target === null || typeof target !== 'object') return 0;

  const propertySet = new Set();
  let curr = target;

  // 沿着原型链逐层向上回溯,直至顶层 Object.prototype
  while (curr && curr !== Object.prototype) {
    // 收集当前层的所有可枚举属性
    Object.keys(curr).forEach(key => propertySet.add(key));
    // 移动到上一级原型
    curr = Object.getPrototypeOf(curr);
  }

  return propertySet.size;
}

console.log(getAllPropertyCount(dog)); // 输出: 4 (name, age, type, breathe)

六、容易混淆的认知盲区与避坑解析

1. 为什么“key in obj”返回 true,Object.keys 却统计不到?

很多开发者容易把 'type' in dog 当作判断属性是否属于该对象的依据。实际上,in 操作符与 dog.type 机制一致,都会递归检查整条原型链。要精准检测某个属性是否为对象自身的非继承属性,应当使用现代标准的 Object.hasOwn(obj, key)(或旧式的 obj.hasOwnProperty(key))。

2. 原型链顶层的 Object.prototype 为什么没被 for...in 算进去?

任何普通对象的最顶层原型都是 Object.prototype,其上挂载了 toString、valueOf、isPrototypeOf 等诸多方法。既然 for...in 会遍历原型链,为什么这些方法没有出现在统计结果里?

原因在于:JavaScript 原生内置在 Object.prototype 上的这些方法,其属性描述符中的 enumerable 默认被设置为了 false(不可枚举)。for...in 仅遍历可枚举属性,因此它们被自动过滤掉了。但是,如果第三方库或业务代码恶意或不规范地向 Object.prototype 上扩展了可枚举属性,for...in 就会立刻将其计入长度,这就是常见的原型链污染漏洞。

3. 子属性与父属性同名时,长度如何计算?

如果我们在 dog 实例自身也声明一个 type: 'husky',此时 dog 自身和原型 animal 都存在名为 type 的属性。根据 JavaScript 的遮蔽规则,实例自身的 type 覆盖了原型的 type。无论通过 for...in 还是通过 Set 统计,该属性在对象外部均表现为一个统一的键,计数值始终只增加 1,绝不会重复累计。

七、总结与最佳实践守则

回到本文探讨的核心问题——“JS获取对象长度时是否包含继承属性?”,其全景知识体系与安全指引可归纳为以下要点:

JS获取对象长度时是否包含继承属性
图4:JavaScript 对象长度与属性体系全景总结图
生产环境编码建议
  • 默认首选 Object.keys(obj).length:语义明确、执行高效,天然隔离原型继承属性,完全杜绝原型污染带来的统计偏差。
  • 避免使用未加过滤的 for...in:如果因特殊兼容性场景使用了 for...in,必须搭配 Object.hasOwn(obj, key) 进行自有属性断言。
  • 处理特殊键使用现代反射 API:如果需要统计不可枚举属性或 Symbol 属性,选用 Reflect.ownKeys(obj).length,它同样安全地停留在自有属性层级。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章