Enumerability and ownership of properties
Mỗi thuộc tính trong các đối tượng JavaScript có thể được phân loại theo ba yếu tố:
- Enumerable (liệt kê được) hoặc non-enumerable (không liệt kê được);
- String hoặc symbol;
- Own property (thuộc tính riêng) hoặc inherited property (thuộc tính kế thừa) từ chuỗi prototype.
Enumerable properties là những thuộc tính có cờ enumerable bên trong được đặt thành true, đây là mặc định cho các thuộc tính được tạo thông qua gán đơn giản hoặc thông qua trình khởi tạo thuộc tính. Các thuộc tính được định nghĩa thông qua Object.defineProperty và các phương thức tương tự mặc định không phải là enumerable. Hầu hết các phương tiện lặp (chẳng hạn như vòng lặp for...in và Object.keys) chỉ duyệt qua các khóa enumerable.
Ownership (quyền sở hữu) của các thuộc tính được xác định bởi việc thuộc tính có thuộc trực tiếp về đối tượng hay không, chứ không phải thuộc chuỗi prototype của nó.
Tất cả các thuộc tính, dù enumerable hay không, string hay symbol, own hay inherited, đều có thể được truy cập bằng ký hiệu chấm hoặc ký hiệu dấu ngoặc vuông. Trong phần này, chúng ta sẽ tập trung vào các phương tiện mà JavaScript cung cấp để duyệt qua một nhóm các thuộc tính đối tượng từng cái một.
Truy vấn thuộc tính của đối tượng
Có bốn cách tích hợp để truy vấn một thuộc tính của đối tượng. Tất cả đều hỗ trợ cả khóa string và symbol. Bảng sau tóm tắt khi nào mỗi phương thức trả về true.
| Enumerable, own | Enumerable, inherited | Non-enumerable, own | Non-enumerable, inherited | |
|---|---|---|---|---|
propertyIsEnumerable() |
true ✅ |
false ❌ |
false ❌ |
false ❌ |
hasOwnProperty() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
Object.hasOwn() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
in |
true ✅ |
true ✅ |
true ✅ |
true ✅ |
Duyệt qua các thuộc tính của đối tượng
Có nhiều phương thức trong JavaScript duyệt qua một nhóm các thuộc tính của đối tượng. Đôi khi, các thuộc tính này được trả về dưới dạng mảng; đôi khi chúng được lặp từng cái một trong một vòng lặp; đôi khi chúng được sử dụng để xây dựng hoặc thay đổi một đối tượng khác. Bảng sau tóm tắt khi nào một thuộc tính có thể được duyệt.
Các phương thức chỉ duyệt qua thuộc tính string hoặc chỉ thuộc tính symbol sẽ có ghi chú thêm. ✅ có nghĩa là thuộc tính thuộc loại này sẽ được duyệt; ❌ có nghĩa là không.
| Enumerable, own | Enumerable, inherited | Non-enumerable, own | Non-enumerable, inherited | |
|---|---|---|---|---|
Object.keysObject.valuesObject.entries |
✅ (strings) |
❌ | ❌ | ❌ |
Object.getOwnPropertyNames |
✅ (strings) |
❌ | ✅ (strings) |
❌ |
Object.getOwnPropertySymbols |
✅ (symbols) |
❌ | ✅ (symbols) |
❌ |
Object.getOwnPropertyDescriptors |
✅ | ❌ | ✅ | ❌ |
Reflect.ownKeys |
✅ | ❌ | ✅ | ❌ |
for...in |
✅ (strings) |
✅ (strings) |
❌ | ❌ |
Object.assign(After the first parameter) |
✅ | ❌ | ❌ | ❌ |
| Object spread | ✅ | ❌ | ❌ | ❌ |
Lấy thuộc tính theo enumerability/ownership
Lưu ý rằng đây không phải là thuật toán hiệu quả nhất cho mọi trường hợp, nhưng hữu ích để minh họa nhanh.
- Phát hiện có thể xảy ra bằng
SimplePropertyRetriever.theGetMethodYouWant(obj).includes(prop) - Lặp có thể xảy ra bằng
SimplePropertyRetriever.theGetMethodYouWant(obj).forEach((value, prop) => {});(hoặc dùngfilter(),map(), v.v.)
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;
},
};