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...inObject.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.keys
Object.values
Object.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ùng filter(), map(), v.v.)
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;
  },
};

Xem thêm