Meet __count__
SpiderMonkey for some time has included a little-known, little-publicized, and little-used property named __count__
on all objects. This property stored the count of the number of enumerable properties directly on the object. For example,
assertEqual({ 1: 1 }.__count__, 1); assertEqual([].__count__, 0); assertEqual([1].__count__, 1); assertEqual([1, /* hole */, 2, 3].__count__, 3);
It’s sort of a convenient way to check property counts, avoiding a verbose for (var p in o)
loop. For example, you could use it to determine how many mappings you had in a hash.
Unfortunately, __count__
has a number of problems.
What’s wrong with __count__
First, and most notably for web developers, __count__
is non-standard. To the best of my knowledge, no other JavaScript engine supports it. Developers must write scripts under the assumption it doesn’t exist, so it provides no brevity bonus. (I recognize extensions and Mozilla-based applications are a special case. For the further reasons below, it’s still worth removing even if some code could assume its existence.)
Second, the special __count__
property contributes to the problem that you can’t actually use an object as a string-value hash. The reason is that you have to be careful that your string-valued keys don’t conflict with special properties, because special properties can’t be overwritten with a custom property. This breaks the nicest feature of using objects for hashes: you can’t just use normal []
property access to set and retrieve mappings. If someone inserts a mapping of, say, "__count__"
→ "special"
, the association with "special"
won’t be preserved; obj.__count__ = 5
is actually a no-op. (NB: Even ignoring __count__
other special properties prevent this from working, but we’re slowly working toward getting rid of them. [Some much more slowly than others, I hasten to note! You don’t need to worry about __proto__
being removed any time in the near future, although you should use Object.getPrototypeOf(obj)
with a compatibility shim to determine obj
‘s prototype in new code.]) A further wrinkle is that __count__
is implemented in such a way that an object literal with "__count__"
as a named property functions differently from an object to which the property is later added by assignment:
var o = { __count__: 17 }; assertEqual(o.__count__, 17); // ...but... var o2 = {}; o2.__count__ = 17; assertEqual(o2.__count__, 17); // fails: __count__ is 0
Third, in supporting __count__
we’ve incurred special-case deoptimization code in SpiderMonkey’s script-to-bytecode compiler. This extra complication, for a feature not often used, does nothing for code readability, complexity, or quality.
Fourth, __count__
doesn’t work the way you might think it works: it’s not uniformly fast for all objects. Property access generally has a syntactic assumption of constant-time speediness. This assumption isn’t valid in languages with getters and setters, but since it’s usually the case, it’s not a horribly inaccurate one. Thus, one might expect that evaluating obj.__count__
is an uncomplicated O(1)
operation which doesn’t allocate memory and just looks up the size of an idealized hash table. It might be possible to make that true, but in fact it never has been true: generally, computing __count__
is O(n)
in the number of properties on the object. Further, because __count__
reuses the same enumeration mechanism as for
loops, it usually requires a memory allocation, which can be slow. __count__
has no asymptotic advantage over manual enumeration of the object’s properties.
In sum, __count__
has problems that mean it doesn’t give you much more than a for (var p in o)
loop would. If that loop were placed in a function, it would be almost identical in code size to use of the property — and it would have the advantage of being completely cross-browser.
__count__
is being removed
We have removed support for __count__
from SpiderMonkey. As a consequence __count__
will also be removed from the next version of Firefox based on trunk Mozilla code. (And, of course, future versions of other Mozilla-based products like SeaMonkey will pick the change up when they produce releases based on trunk Mozilla code.) For the above reasons __count__
doesn’t make much sense to keep around, and it imposes real development costs. You should have no difficulty updating your code to implement alternative functionality to __count__
. Here’s one example of how you might do this:
function count(o) { var n = 0; for (var p in o) n += Object.prototype.hasOwnProperty.call(o, p); return n; } assertEqual(count({ 1: 1 }), 1); assertEqual(count([]), 0); assertEqual(count([1]), 1); assertEqual(count([1, /* hole */, 2, 3]), 3);
If you use __count__
and need to test changes to remove that use, you can experiment with a version of Firefox with support for __count__
removed by downloading a nightly from nightly.mozilla.org. (Don’t forget to use the profile manager if you want to keep the settings you use with your primary Firefox installation pristine.)