JavaScript获取对象属性的方法对比

方法对比表

方法是否包含非迭代属性是否包含原型链属性说明
Object.keys(obj)只返回对象自身的可枚举属性名
Object.getOwnPropertyNames(obj)返回对象自身的所有属性名(包括不可枚举)
for...in 循环遍历对象及其原型链上的可枚举属性
JSON.stringify(obj)只序列化对象自身的可枚举属性

详细说明

1. Object.keys()

const obj = {
  a: 1,
  b: 2
};
 
Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false  // 不可枚举
});
 
console.log(Object.keys(obj)); // ['a', 'b']

特点:

  • 只返回对象自身的可枚举属性
  • 不包含不可枚举属性
  • 不包含原型链上的属性
  • 返回数组格式

2. Object.getOwnPropertyNames()

const obj = {
  a: 1,
  b: 2
};
 
Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false  // 不可枚举
});
 
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c']

特点:

  • 返回对象自身的所有属性(包括不可枚举)
  • 不包含原型链上的属性
  • 返回数组格式
  • 适用于需要完整属性列表的场景

3. for…in 循环

function Parent() {
  this.parentProp = 'parent';
}
Parent.prototype.prototypeProp = 'prototype';
 
function Child() {
  this.childProp = 'child';
}
Child.prototype = new Parent();
 
const obj = new Child();
Object.defineProperty(obj, 'nonEnumerable', {
  value: 'hidden',
  enumerable: false
});
 
for (let key in obj) {
  console.log(key); // 输出: childProp, parentProp, prototypeProp
}

特点:

  • 遍历对象及原型链上的可枚举属性
  • 不包含不可枚举属性
  • 包含继承的属性
  • 需要配合 obj.hasOwnProperty(key) 过滤自身属性

4. JSON.stringify()

const obj = {
  a: 1,
  b: 2
};
 
Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false
});
 
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'

特点:

  • 只序列化可枚举的自身属性
  • 忽略不可枚举属性
  • 忽略原型链属性
  • 返回JSON字符串格式

设置非迭代属性示例

使用 Object.defineProperty()

const obj = {
  a: 1, // 可枚举属性
};
 
// 添加一个非迭代(不可枚举)属性
Object.defineProperty(obj, 'b', {
  value: 2,
  enumerable: false, // 设为非迭代属性
  writable: true,    // 可写
  configurable: true // 可配置
});
 
console.log(Object.keys(obj)); // 输出: ['a']
console.log(Object.getOwnPropertyNames(obj)); // 输出: ['a', 'b']
console.log(obj.b); // 输出: 2 (属性存在,但不可枚举)

批量设置非迭代属性

const obj = { a: 1 };
 
Object.defineProperties(obj, {
  b: {
    value: 2,
    enumerable: false
  },
  c: {
    value: 3,
    enumerable: false
  }
});
 
console.log(Object.keys(obj)); // ['a']
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c']

实际应用场景

1. 库开发中的内部属性

class MyClass {
  constructor() {
    this.publicProp = 'public';
    
    // 设置内部属性为不可枚举
    Object.defineProperty(this, '_internal', {
      value: 'internal',
      enumerable: false,
      writable: true
    });
  }
}
 
const instance = new MyClass();
console.log(Object.keys(instance)); // ['publicProp']
// 内部属性不会在普通遍历中出现

2. 配置对象的元数据

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};
 
// 添加元数据,但不希望在序列化时包含
Object.defineProperty(config, '_version', {
  value: '1.0.0',
  enumerable: false
});
 
console.log(JSON.stringify(config)); 
// '{"apiUrl":"https://api.example.com","timeout":5000}'
// _version 不会被序列化

3. 检查对象的完整属性

function getAllProperties(obj) {
  const ownEnumerable = Object.keys(obj);
  const ownNonEnumerable = Object.getOwnPropertyNames(obj)
    .filter(key => !obj.propertyIsEnumerable(key));
  
  const inherited = [];
  for (let key in obj) {
    if (!obj.hasOwnProperty(key)) {
      inherited.push(key);
    }
  }
  
  return {
    ownEnumerable,
    ownNonEnumerable,
    inherited
  };
}

相关概念

属性描述符

const descriptor = Object.getOwnPropertyDescriptor(obj, 'propertyName');
console.log(descriptor);
// {
//   value: ...,
//   writable: true/false,
//   enumerable: true/false,
//   configurable: true/false
// }

属性枚举性检查

obj.propertyIsEnumerable('propertyName'); // true/false
Object.prototype.propertyIsEnumerable.call(obj, 'propertyName');

最佳实践

  1. 遍历自身属性: 使用 Object.keys()Object.getOwnPropertyNames()
  2. 检查属性存在: 使用 obj.hasOwnProperty(key) 而非 key in obj
  3. 库开发: 将内部属性设为不可枚举,避免影响用户代码
  4. 序列化控制: 利用不可枚举属性控制JSON序列化输出
  5. 性能考虑: Object.keys() 通常比 for...in + hasOwnProperty 更快