admin管理员组

文章数量:1194163

I am trying to discover the getters and setters on an object in typescript. I've tried Object.entries() and Object.keys() and neither of these return getters and setters. How can I enumerate these?

Edit: Here is some code that shows the problem:

class ThingWithGetter{
  myProperty = 22;
  get myGetter() {return 1;}
}
const thing = new ThingWithGetter()

// Does not show getter
console.log(Object.keys(thing));

// Does not show getter
const descriptors = Object.getOwnPropertyDescriptors(thing);
console.log(Object.keys(descriptors));

// Does not show getter
console.log(Object.entries(thing))

I am trying to discover the getters and setters on an object in typescript. I've tried Object.entries() and Object.keys() and neither of these return getters and setters. How can I enumerate these?

Edit: Here is some code that shows the problem:

class ThingWithGetter{
  myProperty = 22;
  get myGetter() {return 1;}
}
const thing = new ThingWithGetter()

// Does not show getter
console.log(Object.keys(thing));

// Does not show getter
const descriptors = Object.getOwnPropertyDescriptors(thing);
console.log(Object.keys(descriptors));

// Does not show getter
console.log(Object.entries(thing))
Share Improve this question edited Feb 25, 2020 at 18:14 Eric Jorgensen asked Feb 25, 2020 at 17:20 Eric JorgensenEric Jorgensen 2,0522 gold badges15 silver badges25 bronze badges 3
  • developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… – kaya3 Commented Feb 25, 2020 at 17:21
  • @kaya3, that does not work for getters and setters – Eric Jorgensen Commented Feb 25, 2020 at 17:54
  • That's because you defined the getter on the class, not the object. When I wrote the comment, your question said you wanted to discover them on the object. – kaya3 Commented Feb 25, 2020 at 18:03
Add a comment  | 

5 Answers 5

Reset to default 10

You can enumerate the getter name with this function:

function listGetters (instance) {
  return Object.entries(
    Object.getOwnPropertyDescriptors(
      Reflect.getPrototypeOf(instance)
    )
  )
  .filter(e => typeof e[1].get === 'function' && e[0] !== '__proto__')
  .map(e => e[0]);
}

It will returns an array containing the getters names.

The reason this doesn't work is that the getter is on the class's prototype, which is on the instance's prototype chain, rather than on the instance itself.

To illustrate:

class A {
  get x() { return 1 }
}

aInstance = new A()
// A {}

Object.getOwnPropertyDescriptors(aInstance)
// {}

Object.getOwnPropertyDescriptors(A.prototype)
// { constructor:
//    { value: [Function: A],
//      writable: true,
//      enumerable: false,
//      configurable: true },
//   x:
//    { get: [Function: get x],
//      set: undefined,
//      enumerable: false,
//      configurable: true } }

Object.getOwnPropertyDescriptors(Object.getPrototypeOf(aInstance))
// { constructor:
//    { value: [Function: A],
//      writable: true,
//      enumerable: false,
//      configurable: true },
//   x:
//    { get: [Function: get x],
//      set: undefined,
//      enumerable: false,
//      configurable: true } }

There a few good blog posts by Axel Rauschmayer on this topic:

  • https://2ality.com/2011/06/prototypes-as-classes.html
  • https://2ality.com/2012/11/property-assignment-prototype-chain.html

Note that if you are trying to enumerate all the properties, accessors, and methods on an object you will need to recursively walk the prototype chain until an object has a null prototype.

The function Object.getOwnPropertyDescriptors is in the ECMAScript latest draft specification, but is implemented in popular browsers. It returns the property descriptors belonging to an object.

Your use-case is a bit more difficult because the getter is defined on the class, not the object itself. So, you need to walk the prototype chain and build it that way. It's not enough to look at just the object's prototype, because the getter could be inherited from any superclass of the object's class.

Here's a recursive function which does it:

function getAllPropertyDescriptors(obj) {
    if (!obj) {
        return Object.create(null);
    } else {
        const proto = Object.getPrototypeOf(obj);
        return {
            ...getAllPropertyDescriptors(proto),
            ...Object.getOwnPropertyDescriptors(obj)
        };
    }
}

The output for JSON.stringify(getAllPropertyDescriptors(thing)) is below. myGetter is the third property descriptor; the actual output if you don't JSON.stringify it also includes references to the actual functions, so you can see if they have get/set properties.

{
  "myProperty": {"value": 22, "writable": true, "enumerable": true, "configurable": true},
  "constructor": {"writable": true, "enumerable": false, "configurable": true},
  "myGetter": {"enumerable": false, "configurable": true},
  "__defineGetter__": {"writable": true, "enumerable": false, "configurable": true},
  "__defineSetter__": {"writable": true, "enumerable": false, "configurable": true},
  "hasOwnProperty": {"writable": true, "enumerable": false, "configurable": true},
  "__lookupGetter__": {"writable": true, "enumerable": false, "configurable": true},
  "__lookupSetter__": {"writable": true, "enumerable": false, "configurable": true},
  "isPrototypeOf": {"writable": true, "enumerable": false, "configurable": true},
  "propertyIsEnumerable": {"writable": true, "enumerable": false, "configurable": true},
  "toString": {"writable": true, "enumerable": false, "configurable": true},
  "valueOf": {"writable": true, "enumerable": false, "configurable": true},
  "__proto__": {"enumerable": false, "configurable": true},
  "toLocaleString": {"writable": true, "enumerable": false, "configurable": true}
}

You could convert this into an iterative version, but that's probably unnecessary since most prototype chains are short, and the iterative version would need some wrangling to get overrides in the right order.

You can try something like:

var obj = {
  get foo() {
    return Math.random() > 0.5 ? 'foo' : 'bar';
  }
};

Object.keys(obj).forEach(key => {
  console.log("Getter:", Object.getOwnPropertyDescriptor(obj, key).get);
  console.log("Setter: ", Object.getOwnPropertyDescriptor(obj, key).set);
})

Hope this works for you.

I had similar task, but I wanted to list only object's regular methods (without getters and setters). It ended up with implementation to list keys of any type you want:

//
// returns array of string: object's string properties (not include symbol properties)
//
// NOTE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
//       https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
//
//  flags:
//   0x1 -> only own (ignore prototype chain)
//
//   0x2 -> only enumerable (ignore non enumerable)
//   0x4 -> only non-enumerable
//
//   0x8  -> only writeable (ignore readonly values)
//   0x10 -> only readonly (ignore writeable values)
//
//   0x20 -> only configurable (ignore object property non-writable/deletable)
//   0x40 -> only non-configurable
//
//   0x80   -> ignore getters: class { get property() { ... } }
//   0x100  -> ignore setters: class { set property(v) { ... } }
//   0x200  -> ignore functions (ignore when propery value is regular function)
//   0x400  -> ignore undefined (ignore when property value is undefined)
//   0x800  -> ignore nulls (ignore when propery value is null)
//   0x1000 -> ignore regular values (ignore when property is regular value: non getter/setter/function/undefined/null)
//
Object.get_keys = function(obj, flags = 0) {
  if (obj === undefined || obj == null) return []; // no properties...
  const ret = new Set();

  do {
    for (const key of Object.getOwnPropertyNames(obj)) { // for each own key
      const descriptor = Object.getOwnPropertyDescriptor(obj, key);

      if (flags & 0x2) { if (!descriptor.enumerable) continue; } // if only enumerable
      else if (flags & 0x4) { if (descriptor.enumerable) continue; } // if not enumerable

      if (flags & 0x20) { if (!descriptor.configurable) continue; } // if only configurable
      else if (flags & 0x40) { if (descriptor.configurable) continue; } // if non configurable

      if ((flags & 0x80) != 0 && Object.hasOwn(descriptor, "get") && typeof(descriptor.get) == "function") continue; // if ignore getters
      if ((flags & 0x100) != 0 && Object.hasOwn(descriptor, "set") && typeof(descriptor.set) == "function") continue; // if ignore setters

      if (Object.hasOwn(descriptor, "value")) {
        if (flags & 0x8) { if (!descriptor.writable) continue; } // if only writeable
        else if (flags & 0x10) { if (descriptor.writable) continue; } // if only readonly

        if (typeof(descriptor.value) == "function") { if (flags & 0x200) continue; }
        else if (descriptor.value === undefined) { if (flags & 0x400) continue; }
        else if (descriptor.value === null) { if (flags & 0x800) continue; }
        else { if (flags & 0x1000) continue; }
      }

      ret.add(key);
    }

    if (flags & 1) break; // if 'only own' => break
    obj = Object.getPrototypeOf(obj); // follow to internal object (from inheritance)
  }
  while (obj);

  return Array.from(ret);
};

//
// returns array of string: only object's regular methods keys
//
Object.get_keys_methods = function(obj, flags = 0) {
  return Object.get_keys(obj, (flags & 0x7F) | 0x1D80); // ignore: getters+setters+undefined+null+regular_values
};

Object.get_keys_accessors = function(obj, flags = 0) {
  return Object.get_keys(obj, (flags & 0x7F) | 0x1E00); // ignore: functions+undefined+null+regular_values
};

In you case it is: get_key_accessors.

My current Chrome version gives:

Object.get_keys_methods(window) // returns: 994 keys (almost all are global constructors)
Object.get_keys_accessors(window) // returns: 184 keys

Object.get_keys_methods(document) // returns: 90 keys
Object.get_keys_accessors(document) // returns: 199 keys

本文标签: how to enumeratediscover getters and setters in javascriptStack Overflow