js对象属性的遍历有很多方法,比如for in、Object.keys、Reflect.ownKeys等等,用起来不知道选哪个,记起来麻烦,本文将介绍所有属性访问相关api的能力和区别。
前置知识:对象属性的「3 种分类」
所有遍历方法的核心差异,本质都是「能遍历哪些类型的属性」,必须先明确对象属性的 3 类划分:
1. 可枚举属性 vs 不可枚举属性
属性的 enumerable 特性为 true 是「可枚举属性」,false 是「不可枚举属性」;
- 默认:我们直接给对象定义的属性(
obj.name = 'xxx')、字面量声明的属性,默认可枚举; - 不可枚举:JS 内置属性(比如
obj.toString、obj.__proto__)、通过Object.defineProperty手动设置enumerable: false的属性,都是不可枚举属性。
2. 自有属性(自有属性) vs 继承属性
- 自有属性:对象自己本身拥有 的属性(挂载在对象自身,不在
__proto__原型链上); - 继承属性:从原型链 上继承过来的属性(比如
obj.hasOwnProperty、obj.toString都是从Object.prototype继承的)。
3. 字符串属性 vs Symbol 属性
JS 对象的属性名,可以是「字符串」或「Symbol 类型」(ES6 新增);
- 字符串属性:最常用的
obj.name、obj['age']都属于此类; - Symbol 属性:
const s = Symbol('id'); obj[s] = 100,这种属性名是唯一的,默认不会被常规遍历方法捕获。
不同属性访问(遍历/判断)方法区别
| 方法 | 访问范围 | 能否访问 Symbol | 能否访问不可枚举 | 能否访问继承属性 | 返回值 / 形式 |
|---|---|---|---|---|---|
JSON.stringify |
可枚举字符串属性 | 否 | 否 | 否 | JSON字符串 |
for...in |
可枚举字符串属性 | 否 | 否 | 否 | 循环,直接拿 key |
Object.keys |
自有可枚举字符串属性 | 否 | 否 | 否 | 字符串数组 |
Object.values |
自有可枚举字符串属性 | 否 | 否 | 否 | 值的数组 |
Object.entries |
自有可枚举字符串属性 | 否 | 否 | 否 | 二维键值对数组 |
Object.getOwnPropertyNames |
自有所有字符串属性 | 否 | 否 | 否 | 字符串数组 |
Object.getOwnPropertySymbols |
自有所有 Symbol 属性 | 是 | 是 | 否 | Symbol 数组 |
Reflect.ownKeys |
自有所有属性 | 是 | 是 | 否 | 混合类型数组 |
Object.getOwnPropertyDescriptors |
自有所有属性 | 是 | 是 | 否 | 属性描述符对象 |
hasOwnProperty |
自有所有属性 | 是 | 是 | 否 | 布尔 |
Object.hasOwn |
自有所有属性 | 是 | 是 | 否 | 布尔 |
in |
所有属性 | 是 | 是 | 是 | 布尔 |
Reflect.has |
所有属性 | 是 | 是 | 是 | 布尔 |
规律小结
- 最局限的5个方法刚好也是最常用的
JSON.stringify,for...in,Object.keys,Object.values,Object.entries,既无法访问symbol属性,也无法访问不可枚举和继承属性。 - 带
own的api,都无法访问继承属性 in操作符和Reflect.has可以访问到所有属性
关于继承的说明
本文中的继承,是指原型链访问,并非面向对象中的类继承(extends)。
js
class User {
name = "小王"; //自有属性
work() {}
}
const p = new User();
这里的p对象的name属性,是它的自有属性,而work方法继承自原型链,因此own类方法无法访问到work。

es6中的继承,沿用了es5中的属性私有,方法公开的继承最佳实践,因此es6中extends自父类的成员属性,也是该实例自有属性。
附
测试代码和截图如下
js
class Persion {
alive = true; //继承属性
study() {}
}
class User extends Persion {
name = "小王"; //自有属性
work() {}
}
const p = new User();
//wealth 不可枚举
Object.defineProperty(p, "wealth", {
enumerable: false,
writable: true,
value: 1000000,
});
p.age = 18; //age自有属性
const CAREER = Symbol("career");
const ID = Symbol("id");
p[ID] = "9523"; //ID symbol属性
Object.defineProperty(p, CAREER, {
enumerable: false,
writable: true,
value: "detective",
});
console.group("for in");
for (const key in p) {
console.log(key); // alive name age
}
console.groupEnd("for in");
console.log(Object.keys(p)); // ['alive', 'name', 'age']
console.log(Object.values(p)); //[true, '小王', 18]
console.log(Object.entries(p)); // '[["alive",true],["name","小王"],["age",18]]'
// -----------------------可得到非symbol自有属性,无法获取不可枚举属性
console.log("Object.getOwnPropertyNames", Object.getOwnPropertyNames(p)); // ['alive', 'name', 'wealth', 'age']
//------------------------可得到symbol属性,包括不可枚举的symbol
console.log(
"Object.getOwnPropertySymbols",
Object.getOwnPropertySymbols(p)
); //[Symbol(id),Symbol(career)]
//------------------------可得到所有自有属性,包括不可枚举和symbol属性,无法获取继承属性
console.log("Reflect.ownKeys", Reflect.ownKeys(p)); // ['alive', 'name', 'wealth', 'age', Symbol(id),Symbol(career)]
//------------------------可得到所有自有属性,包括不可枚举和symbol属性,无法获取继承属性
console.log(
"Object.getOwnPropertyDescriptors",
Object.getOwnPropertyDescriptors(p)
);
//继承属性返回false
console.group("p.hasOwnProperty");
["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
(name) => {
console.log(
`p.hasOwnProperty(${name.toString()})`,
p.hasOwnProperty(name)
);
}
);
console.groupEnd();
//继承属性返回false
console.group("Object.hasOwn");
["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
(name) => {
console.log(
`Object.hasOwn(p, ${name.toString()})`,
Object.hasOwn(p, name)
);
}
);
console.groupEnd();
// 可判断所有属性,包括不可枚举/symbol/继承
console.group("in");
["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
(name) => {
console.log(`${name.toString()} in p`, name in p);
}
);
console.groupEnd();
// 可判断所有属性,包括不可枚举/symbol/继承
console.group("Reflect.has");
["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
(name) => {
console.log(
`Reflect.has(p, ${name.toString()})`,
Reflect.has(p, name)
);
}
);
console.groupEnd();
