A well-known fact is that you can use any1 JavaScript object as a dictionary (map) from strings to objects.
> var dict = {};
> dict.key1 = "foo";
> dict["key2"] = "bar";
This works well as long as the keys are constants, or in general under your control. However, things don’t work as well when you start allowing arbitrary, user-controlled keys. Say you use the tags defined in Thunderbird as keys. What happens if a user defines a tag named “watch”?
> var dict = {};
> ("watch" in dict)
true
You might suspect that hasOwnProperty fixes this, and it does, but
- it’s clunky
- it doesn’t fix other magical keywords like
__proto__(though I’d like to know what sort of user has a tag named__proto__:) )
Even worse, since __proto__ is both non-enumerable and non-configurable, it’ll be lost in a for..in loop.
There are other issues too: subtle changes in semantics because of a key named toString, for instance. What if a programmer has a debug statement printing out a dictionary somewhere (not that it’s generally too useful)?
Clearly, plain objects don’t cut it for arbitrary keys, so we in Thunderbird needed something a little more sophisticated. We still wanted to retain most of the speed of direct property access, though. I spent a weekend investigating what we could do, and what was finally agreed upon was a lightweight wrapper around objects.
The API (get/set/has/del) is inspired by, but somewhat different from, Python’s dict API. Of course, now that we’re using a wrapper anyway, we can add convenience stuff to make using it more enticing:
- separate iterators over keys, values and key-value pairs
- a working
toStringfunction! - a
countproperty
We’ve landed this in comm-central and it’s available to any Thunderbird, SeaMonkey or extension developers who want to use it – simply use For examples, see the tests.Components.utils.import("resource:///modules/dictUtils.js"); to import it into your scope.
Here’s the GitHub repository where I experimented, including an aborted attempt to use ECMAScript 5 proxies.
Update (23/3/11): The module’s in mozilla-central now! To use it, add the line
Components.utils.import("resource://gre/modules/Dict.jsm");
to your scope.
1 You should only be using plain objects as dictionaries, though. In particular, you shouldn’t be using JavaScript’s Array objects as dictionaries.