I'm fairly new at JavaScript, and I'm finding that JS is a language with what seems like an unusually high number of gotchas for the inexperienced, so some criticisms of this code would be particularly appreciated.
The function getProperties
below is a relatively simple function that returns an array of the names (not the values) of all the properties that an object has (either "owned" or "inherited"). Here's the code, followed by some description of what it does:
function getProperties(o) {
var seenobj = new Set();
var seenprop = new Set();
function _proto(obj) {
return obj instanceof Object ?
Object.getPrototypeOf(obj) :
obj.constructor.prototype;
}
function _properties(obj) {
var ret = [];
if (obj === null || seenobj.has(obj)) { return ret; }
seenobj.add(obj);
if (obj instanceof Object) {
var ps = Object.getOwnPropertyNames(obj);
for (var i = 0; i < ps.length; ++i) {
if (!seenprop.has(ps[i])) {
ret.push(ps[i]);
seenprop.add(ps[i]);
}
}
}
return ret.concat(_properties(_proto(obj)));
}
return _properties(o);
}
This function has four local variables: _proto
, _properties
, seenobj
, seenprop
. The first one, _proto
is a helper function to streamline computing an object's prototype. The second one, _properties
, is a recursive function, and it actually does all the work. The third one, seenobj
, is a (ECMAScript 6) Set
object used to keep track of the objects already encountered in the recursion (and therefore avoid infinite recursion). The fourth one, seenprop
, also a Set
object, similarly keeps track of the names of properties already seen, and thus ensures that property names are not repeated in the returned value. (NOTE: Set
objects are available only in a few browsers. An obvious improvement would be to replace these objects with a more portable alternative.)
If the argument obj
of _properties
is null
, or if it is already present in seenobj
, _properties
returns an empty array. (These are the conditions that terminates the recursion.)
Otherwise, if obj
is an instance of Object
, _properties
gets the list of its properties with Object.getOwnPropertyNames
. _properties
then returns as its result the array consisting of those properties of obj
(if any) that are not already in the seen
variable concatenated with the array returned by a recursive call with Object.getPrototypeOf(obj)
as argument.
Here's getProperties
applied to some standard items (the interaction shown is from the Firefox/Firebug console, though I've added line breaks in the output to avoid long lines):
>>> getProperties({})
[]
>>> getProperties(0)
["constructor", "toSource", "toString", "toLocalString", "toFixed", "toExponential",
"toPrecision"]
>>> getProperties("")
["length", "constructor", "quote", "toSource", "toString", "valueOf", "substring",
"toLowerCase", "toUpperCase", "charAt", "charCodeAt", "contains", "indexOf", "lastIndexOf",
"startsWith", "endsWith", "trim", "trimLeft", "trimRight", "toLocaleLowerCase",
"toLocaleUpperCase", "localeCompare", "match", "search", "replace", "split", "substr",
"concat", "slice", "bold", "italics", "fixed", "fontsize", "fontcolor", "link", "anchor",
"strike", "small", "big", "blink", "sup", "sub", "iterator"]
>>> getProperties([])
["length", "constructor", "toSource", "toString", "toLocaleString", "join", "reverse",
"sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "indexOf",
"lastIndexOf", "forEach", "map", "reduce", "reduceRight", "filter", "some", "every",
"iterator"]
>>> getProperties(function () {})
["prototype", "length", "name", "arguments", "caller", "constructor", "toSource",
"toString", "apply", "call", "bind", "isGenerator"]
Thanks for reading this. I look forward to your comments.
EDIT: The code shown above incorporates fixes to various problems that I noticed after posting the original version. This original version is shown below, FWIW.
function getProperties(o) {
var seen = {};
function _properties(obj) {
var ret = [];
if (obj === null) {
return ret;
}
try {
var ps = Object.getOwnPropertyNames(obj);
}
catch (e if e instanceof TypeError &&
e.message === "obj is not an object") {
return _properties(obj.constructor.prototype);
}
for (var i = 0; i < ps.length; ++i) {
if (typeof seen[ps[i]] === "undefined") {
ret.push(ps[i]);
seen[ps[i]] = true;
}
}
return ret.concat(_properties(Object.getPrototypeOf(obj)));
}
return _properties(o);
}