Search code examples
javascriptecmascript-6es6-proxy

Why does Object.keys() and Object.getOwnPropertyNames() produce different output when called on a Proxy object with ownKeys handler?


I have the following proxy:

const p = new Proxy({}, {
  ownKeys(target) {
    return ['a', 'b'];
  },
});

MDN says that:

This trap can intercept these operations:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • Reflect.ownKeys()

Therefore, I expected Object.getOwnPropertyNames() and Object.keys() to produce the same output. However, Object.getOwnPropertyNames(p) returns ['a', 'b'] (as expected), but Object.keys(p) returns an empty array. Why is that?

Also, if I add a property to this object which is not returned by the ownKeys handler (for example c), it gets ignored by both functions (they don't change their output). However, when I add a property which is returned by the ownKeys handler (for example a), Object.keys(p) now returns ['a'].

Code snippet:

const p = new Proxy({}, {
  ownKeys(target) {
    return ['a', 'b'];
  },
});

console.log(Object.getOwnPropertyNames(p)); // ['a', 'b']
console.log(Object.keys(p)); // []

p.c = true;
console.log(Object.getOwnPropertyNames(p)); // ['a', 'b']
console.log(Object.keys(p)); // []

p.a = true;
console.log(Object.getOwnPropertyNames(p)); // ['a', 'b']
console.log(Object.keys(p)); // ['a']


Solution

  • The difference between Object.keys() and Object.getOwnPropertyNames() is that Object.keys() will invoke [[GetOwnProperty]] on the object, and only add the property to the result list if the returned property descriptor is enumerable. Since the object doesn't have a such a property, [[GetOwnProperty]] will return undefined and the property (name) is ignored.

    You can overwrite/implement [[GetOwnProperty]] in the proxy by implementing getOwnPropertyDescriptor:

    const p = new Proxy({}, {
      ownKeys(target) {
        return ['a', 'b'];
      },
      getOwnPropertyDescriptor(k) {
        return {
          enumerable: true,
          configurable: true,
        };
      }
    });
    
    console.log(Object.getOwnPropertyNames(p)); // ['a', 'b']
    console.log(Object.keys(p)); // ['a', 'b']