Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active February 7, 2023 02:16
Show Gist options
  • Select an option

  • Save dfkaye/d194247482ba4c580510 to your computer and use it in GitHub Desktop.

Select an option

Save dfkaye/d194247482ba4c580510 to your computer and use it in GitHub Desktop.
Getting enumerable and non-enumerable properties on prototypes and built-ins in ES5

Getting enumerable and non-enumerable properties on prototypes and built-ins

TIL that I've been doing it all wrong with respect to object API reflection. I had not taken into account that some properties are defined as non-enumerable by default, such as those on the prototype of native or built-in APIs.

For example, when you want to verify that the String.prototype contains a certain method, the for..in loop and Object.keys() method do not work. Use Object.getOwnPropertyNames() instead.

Here's a quick tour of Object.keys(), the for...in loop, and Object.getOwnPropertyNames()

Object.keys()method

The Object.keys() method returns an array of a given object's own enumerable properties.

In short, Object.keys() returns keys for items that are enumerable on an object, and are "own" keys (i.e., that are not inherited from the object's prototype).

function inspect(obj) {
  var a = Object.keys(obj);
  console.log(a);
  return a;
}

inspect('hello'); //  ["0", "1", "2", "3", "4"]
inspect({ name: ''}); //  ["name"]
inspect(String); //  []

// inherited
function Class(v) {
  this.v = v;
};
Class.prototype = {x: '1', y: '2', z: '9'};
inspect(Class); //  []
inspect(Class.prototype); // ["x", "y", "z"]
inspect(new Class('value')); //  ["v"]

The for...in loop

The for...in statement iterates over the enumerable properties of an object, in arbitrary order… The loop will iterate over all enumerable properties of the object itself and those the object inherits from its constructor's prototype.

function inspect(obj) {
  var a = [];
  for (var k in obj) {
    a.push(k);
  }
  console.log(a);
  return a;
}

inspect('hello'); //  ["0", "1", "2", "3", "4"]
inspect({ name: ''}); //  ["name"]
inspect(String); //  []

// inherited
function Class(v) {
  this.v = v;
};
Class.prototype = {x: '1', y: '2', z: '9'};
inspect(Class); //  []
inspect(Class.prototype); // ["x", "y", "z"]

However, this call finds keys x, y, z which are inherited from the prototype chain:

inspect(new Class('value')); //  ["v", "x", "y", "z"]

for...in with hasOwnProperty()

To duplicate the behavior of Object.keys() (or polyfill it), check that each key is an "ownProperty" of the object, using hasOwnProperty():

function inspect(obj) {
  var a = [];
  for (var k in obj) {
    obj.hasOwnProperty(k) && (a.push(k));
  }
  console.log(a);
  return a;
}

inspect('hello'); //  ["0", "1", "2", "3", "4"]
inspect({ name: ''}); //  ["name"]
inspect(String); //  []

// inherited
function Class(v) {
  this.v = v;
};
Class.prototype = {x: '1', y: '2', z: '9'};
inspect(Class); //  []
inspect(Class.prototype); // ["x", "y", "z"]

Now the keys x, y, z which are inherited from the prototype chain are skipped:

inspect(new Class('value')); //  ["v"]

Object.getOwnPropertyNames()

Object.getOwnPropertyNames() returns an array whose elements are strings corresponding to the enumerable and non-enumerable properties found directly upon obj.

Use this when you want to query a native or built-in object or function for its API of "own" properties:

function inspect(obj) {
  var a = Object.getOwnPropertyNames(obj);
  console.log(a);
  return a;
}

inspect('hello'); //   ["length", "0", "1", "2", "3", "4"]
inspect({ name: ''}); //  ["name"]
inspect(String); //  ["prototype", "toLowerCase", "toUpperCase", "charAt", "charCodeAt", "includes", "contains", "indexOf", "lastIndexOf", "startsWith", "endsWith", "trim", "trimLeft", "trimRight", "toLocaleLowerCase", "toLocaleUpperCase", "normalize", "match", "search", "replace", "split", "concat", "fromCharCode", "fromCodePoint", "raw", "substring", "substr", "slice", "localeCompare", "length", "name"]
inspect(String.prototype); //  ["length", "constructor", "toSource", "toString", "valueOf", "toLowerCase", "toUpperCase", "charAt", "charCodeAt", "substring", "codePointAt", "includes", "contains", "indexOf", "lastIndexOf", "startsWith", "endsWith", "trim", "trimLeft", "trimRight", "toLocaleLowerCase", "toLocaleUpperCase", "localeCompare", "repeat", "normalize", "match", "search", "replace", "split", "substr", "concat", "slice", "bold", "italics", "fixed", "strike", "small", "big", "blink", "sup", "sub", "anchor", "link", "fontcolor", "fontsize"]

// inherited
function Class(v) {
  this.v = v;
};
Class.prototype = {x: '1', y: '2', z: '9'};
inspect(Class); //   ["toSource", "toString", "apply", "call", "bind", "isGenerator", "arguments", "caller", "constructor", "length", "name"]
inspect(Class.prototype); // ["x", "y", "z"]

Again, keys x, y, z inherited from the prototype chain are skipped:

inspect(new Class('value')); //  ["v"]

Live and learn…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment