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()…
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...instatement 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"]
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() 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…