属性的可枚举性和所有权
JavaScript 对象中的每个属性都可以通过三个因素进行分类
- 可枚举或不可枚举;
- 字符串或符号;
- 自有属性或从原型链继承的属性。
可枚举属性是指其内部可枚举标志设置为 true 的属性,这是通过简单赋值或属性初始化器创建的属性的默认值。通过Object.defineProperty
等方法定义的属性默认情况下是不可枚举的。大多数迭代方式(例如for...in
循环和Object.keys
)只访问可枚举的键。
属性的所有权取决于该属性是直接属于对象,而不是属于其原型链。
所有属性,无论是可枚举的还是不可枚举的,字符串还是符号,自有属性还是继承属性,都可以通过点表示法或方括号表示法访问。在本节中,我们将重点介绍 JavaScript 提供的逐一访问一组对象属性的方法。
查询对象属性
有四种内置方法可以查询对象的属性。它们都支持字符串和符号键。下表总结了每种方法返回true
的情况。
可枚举,自有 | 可枚举,继承 | 不可枚举,自有 | 不可枚举,继承 | |
---|---|---|---|---|
propertyIsEnumerable() |
true ✅ |
false ❌ |
false ❌ |
false ❌ |
hasOwnProperty() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
Object.hasOwn() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
in |
true ✅ |
true ✅ |
true ✅ |
true ✅ |
遍历对象属性
JavaScript 中有许多方法可以遍历对象的一组属性。有时,这些属性作为数组返回;有时,它们在循环中逐一迭代;有时,它们用于构建或修改另一个对象。下表总结了何时可以访问属性。
仅访问字符串属性或仅访问符号属性的方法将有一个额外注释。✅ 表示将访问此类型的属性;❌ 表示不会。
可枚举,自有 | 可枚举,继承 | 不可枚举,自有 | 不可枚举,继承 | |
---|---|---|---|---|
Object.keys Object.values Object.entries |
✅ (字符串) |
❌ | ❌ | ❌ |
Object.getOwnPropertyNames |
✅ (字符串) |
❌ | ✅ (字符串) |
❌ |
Object.getOwnPropertySymbols |
✅ (符号) |
❌ | ✅ (符号) |
❌ |
Object.getOwnPropertyDescriptors |
✅ | ❌ | ✅ | ❌ |
Reflect.ownKeys |
✅ | ❌ | ✅ | ❌ |
for...in |
✅ (字符串) |
✅ (字符串) |
❌ | ❌ |
Object.assign (第一个参数之后) |
✅ | ❌ | ❌ | ❌ |
对象扩展 | ✅ | ❌ | ❌ | ❌ |
根据可枚举性/所有权获取属性
请注意,这并非所有情况下最有效的算法,但对于快速演示很有用。
- 检测可以通过
SimplePropertyRetriever.theGetMethodYouWant(obj).includes(prop)
进行 - 迭代可以通过
SimplePropertyRetriever.theGetMethodYouWant(obj).forEach((value, prop) => {});
进行(或使用filter()
、map()
等)
js
const SimplePropertyRetriever = {
getOwnEnumProps(obj) {
return this._getPropertyNames(obj, true, false, this._enumerable);
// Or could use for...in filtered with Object.hasOwn or just this: return Object.keys(obj);
},
getOwnNonEnumProps(obj) {
return this._getPropertyNames(obj, true, false, this._notEnumerable);
},
getOwnProps(obj) {
return this._getPropertyNames(
obj,
true,
false,
this._enumerableAndNotEnumerable,
);
// Or just use: return Object.getOwnPropertyNames(obj);
},
getPrototypeEnumProps(obj) {
return this._getPropertyNames(obj, false, true, this._enumerable);
},
getPrototypeNonEnumProps(obj) {
return this._getPropertyNames(obj, false, true, this._notEnumerable);
},
getPrototypeProps(obj) {
return this._getPropertyNames(
obj,
false,
true,
this._enumerableAndNotEnumerable,
);
},
getOwnAndPrototypeEnumProps(obj) {
return this._getPropertyNames(obj, true, true, this._enumerable);
// Or could use unfiltered for...in
},
getOwnAndPrototypeNonEnumProps(obj) {
return this._getPropertyNames(obj, true, true, this._notEnumerable);
},
getOwnAndPrototypeEnumAndNonEnumProps(obj) {
return this._getPropertyNames(
obj,
true,
true,
this._enumerableAndNotEnumerable,
);
},
// Private static property checker callbacks
_enumerable(obj, prop) {
return Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_notEnumerable(obj, prop) {
return !Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_enumerableAndNotEnumerable(obj, prop) {
return true;
},
// Inspired by http://stackoverflow.com/a/8024294/271577
_getPropertyNames(obj, iterateSelf, iteratePrototype, shouldInclude) {
const props = [];
do {
if (iterateSelf) {
Object.getOwnPropertyNames(obj).forEach((prop) => {
if (props.indexOf(prop) === -1 && shouldInclude(obj, prop)) {
props.push(prop);
}
});
}
if (!iteratePrototype) {
break;
}
iterateSelf = true;
obj = Object.getPrototypeOf(obj);
} while (obj);
return props;
},
};