64

jshashtable states:

JavaScript's built-in objects do provide hashtable functionality using the square brackets notation for properties, provided your keys are strings or numbers:

From what I know, keys are only strings, (since numbers are coerced into strings anyway). I just want to check and be sure that what is stated above is false (since keys can't be numbers).

Did the ECMA standard state anything about this?

Or is the implementation browser-specific?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pacerier
  • 86,231
  • 106
  • 366
  • 634
  • 1
    I've now updated the jshashtable documentation. As I mentioned in a comment to one of the answers, I was trying to keep things simple but actually it's just fudgy and arguably wrong so you were right to pick up on it. – Tim Down May 20 '11 at 10:54
  • 2
    @Tim Down Still not a fan ;-) Here is a suggested approach: "No. Although JavaScript objects can be used as a hash, there are several limitations which make using JavaScript objects unsuitable for a generic hash. One limitation is that only strings and numbers tend to make useful keys." (Then go on to show cases and explain more details. etc, but leave the intro an intro and avoid committing to "only strings", etc) –  May 20 '11 at 18:17
  • 1
    @pst: Yes, your version is definitely an improvement. I've used it almost word for word, so thank you very much for that and all your input. – Tim Down May 21 '11 at 15:28
  • in a near future, WeakMap will do just that : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap#Why_WeakMap.3F – GameAlchemist Aug 10 '13 at 09:10

8 Answers8

46

JavaScript's built-in objects do provide hashtable functionality using the square brackets notation for properties, provided your keys are strings or numbers

That seems to be incorrect - object keys may be strings or (since ECMAScript 2015, aka ECMA-262 ed 6) symbols. But that is a different topic to square bracket property access.

See ECMA-262 ed 3 § 11.2.1 (Please also see ECMAScript 2017 (draft).):

Properties are accessed by name, using either the dot notation:

MemberExpression . IdentifierName

CallExpression . IdentifierName

or the bracket notation:

MemberExpression [ Expression ]

CallExpression [ Expression ]

The dot notation is explained by the following syntactic conversion:

MemberExpression . IdentifierName

is identical in its behaviour to

MemberExpression [ <identifier-name-string> ]

and similarly

CallExpression . IdentifierName

is identical in its behaviour to

CallExpression [ <identifier-name-string> ]

where <identifier-name-string> is a string literal containing the same sequence of characters after processing of Unicode escape sequences as the IdentifierName.

So when using dot notation, the bit after the dot must fit the criteria for an IdentifierName. But when using square brackets, an expression is provided that is evaluated and resolved to a string.

Briefly, square bracket notation is provided so that properties can be accessed using an expression, e.g.

var y = {};
var x = 'foo';
y[x] = 'foo value';

In the above, x is provided in square brackets so it is evaluated, returning the string 'foo'. Since this property doesn't exist on y yet, it is added. The foo property of y is then assigned a value of 'foo value'.

In general terms, the expression in the square brackets is evaluated and its toString() method called. It is that value that is used as the property name.

In the dot property access method, the identifier is not evaluated, so:

y.bar = 'bar value';

creates a property bar with a value bar value.

If you want to create a numeric property, then:

y[5] = 5;

will evaluate 5, see it's not a string, call (more or less) Number(5).toString() which returns the string 5, which is used for the property name. It is then assigned the value 5, which is a number.

This answer was written when ECMAScript ed3 was current, however things have moved on. Please see later references and MDN.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
RobG
  • 142,382
  • 31
  • 172
  • 209
  • +1 However, the argument (from the quote pulled) isn't valid -- although the conclusion is. `CallExpression.Ident is identical to CallExpression["Ident"]` does not imply that `CallExpression[42] is identical to CallExpression["42"]` (although it is -- and there is conclusive wording in the specification). –  May 20 '11 at 03:38
  • 1
    @pst: The original quote is from me, from a couple of years ago. I was aware when writing it that it's not strictly accurate, but was trying to avoid quoting the spec in an effort to keep things simple. However, it's caused confusion and it now looks unacceptably fudgy to me anyway now, so I'll change it. – Tim Down May 20 '11 at 08:57
  • @pst: jshashtable docs now updated. I still want to avoid getting too detailed and trotting out the spec but I've eliminated the string/number property name business and made a nod in the general direction of objects and primitives. – Tim Down May 20 '11 at 10:54
  • 6
    ok just to be sure, whenever we do array[1] its actually converted to array["1"] ? – Pacerier May 20 '11 at 13:50
  • 2
    @Pacerier: nice wrap-up, that's exactly what I needed to know quickly – Sam Vloeberghs May 01 '14 at 18:01
13

You're right. Keys can only be strings, and numeric keys such as those used in Arrays are coerced and stored as strings.

var arr = [true];
arr[0] === true;
arr['0'] = false;
arr[0] === false;

ECMAScript specification, page 42: ECMA-262 Script 3rd Edition.

The production PropertyName : NumericLiteral is evaluated as follows:

  1. Form the value of the NumericLiteral.
  2. Return ToString(Result(1)).
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matty F
  • 3,763
  • 4
  • 30
  • 48
  • 1
    how do you prove that numbers are coerced into strings, and not the other way round? – Pacerier May 20 '11 at 03:09
  • 3
    This is taken from ECMA Script specification, v3 - page 42: The production PropertyName : NumericLiteral is evaluated as follows: 1. Form the value of the NumericLiteral. 2. Return ToString(Result(1)). – Matty F May 20 '11 at 03:23
  • +1 However, it would be nice to show the rules from Ed.5 as well to put a nail in the coffin. Also, this doesn't show/cover the case of `obj[array]` or similar. (That is a usage rule, not a grammar production.) –  May 20 '11 at 03:41
  • ok just to be sure, whenever we do array[1] its actually converted to array["1"] ? – Pacerier May 20 '11 at 13:51
5

The keys are always strings. This means you can't use an object instance's identity as a key.

In Flash's ActionScript 3 (uses strong run-time types unlike AS 2) there is a Dictionary object which uses strict equality comparison for keys, so that you can use object instances themselves as keys (as well as numbers, strings, etc.).

If you wanted to do the same thing in JavaScript, it would be difficult, because you'd have to generate your own unique object ids and attach them to every object you wanted to track. Some have suggested adding a prototype function to the Object class, but that would add overhead to every object unnecessarily. In any case, you'd want to give an object a trackable ID via a function call that assigns an incrementing static number to a unique property such as "__objectid__".

It would then be conceivable to create a Dictionary-like class with methods like Add(key,value), but it would have to store strings, numbers, and objects in three separate internal hashes to ensure "3" doesn't collide with the number 3 or the object with id 3. The add method would have to automatically assigned an __objectid__ to any key of type object that didn't already have an id assigned. Even after all that, you wouldn't be able to access the dictionary using brackets, unless there are some hooks for property assignments that I'm not aware of in JavaScript.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Triynko
  • 18,766
  • 21
  • 107
  • 173
5

Well, here is my answer—mostly because I was not satisfied with the references in the other (correct) answers—expressions for property names in [ ] are always coerced to strings and this behavior is well defined in the specification. Thus, depending upon interpretation of the quote in question, it can be taken as misleading and/or incorrect.

However, the quote does not presume that x[42] and x["42"] are different; it states—with the misleading exclusion of other primitives and details—that only strings and numbers are usable as "hash keys" (really property names) under normal property resolution and, in this sense, the quote is arguably correct.

These rules are from Standard ECMA-262 ECMAScript Language Specification 5th edition (December 2009).

From section "11.2.1 Property Accessors" (production rules omitted):

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Let baseReference be the result of evaluating MemberExpression.
  2. Let baseValue be GetValue(baseReference).
  3. Let propertyNameReference be the result of evaluating Expression.
  4. Let propertyNameValue be GetValue(propertyNameReference).
  5. Call CheckObjectCoercible(baseValue).
  6. Let propertyNameString be ToString(propertyNameValue).
  7. If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
  8. Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

Here is a functor Array. It is useful when using a functional programming paradigm.

javascript:
    alert(["Using  ",window.navigator.userAgent] );
    FunctorRA=[]; f=function(){return f}; g=function(x){return x};
    FunctorRA[f]=43;
    FunctorRA[g(function(){})]="generic";
    FunctorRA[g(g)]="idempotent";
    alert(FunctorRA);
    for (i in FunctorRA)
        alert([ "Functor[ ", i,
                "]'s\n\n value is \n\n", FunctorRA[i]].join(""));

displays:

Using  ,Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100423
    Ubuntu/10.04 (lucid) Firefox/3.6.3

And an empty alert and then:

Functor[ function () {
    return f;
}]'s

 value is

43

etc.

Nota bene:

  • alert( FunctorRA ) shows .toString() does not enumerate non-numeric indexes
  • FunctorRA is a generic object in array's "clothing"
  • there is no direct . syntactic equivalent (even with string eval coercion). See Dynamic function name in JavaScript for details on how :, a colon, can be embedded in a function name even though it is usually a delimiter to syntactically delineate initializer properties, labels, ?: (conditional expressions), etc. An analogous problem exists with ., requiring the escaping of all syntactically significant JavaScript character codes such as (){}\n ., etc. The [] construct effectively does this for the parenthetically contained expression, as a whole, presumably by deferring the evaluation of the string to be an object of type String. This is similar to the fact 43.x=2 is verboten, but not if 43 is represented as an object of type Number by (43).x=2 or 43["x"]=2. (proof: javascript:alert( [ (43).x=2, 43["x"]=2 ] ) displays 2,2 but javascript:alert(43.x=2) generates an error).
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ekim
  • 949
  • 1
  • 8
  • 9
0

Well, everything that you use for the key is converted to string because of hashing function that hashes strings to a nearly unique short set of bytes that can be interpreted as an integer, cause integer comparison during the linear search is low level and fast.(JavaScript objects are hash tables). You stringify objects and arrays to use them as a key. Keys can be unlimited in size. But beware, because JavaScript objects are unordered.

Yesterday and today, I've tried to wrap my head around the problems appearing in this domain and I've written these solutions. First one is a custom implementation of a hash table, JavaScript using objects as keys. And there everything is explained in layman's terms...

Anther one is an upgrade to the first one, and it is a deterministic JSON stringify that stringifies objects with alphabetically ordered properties: JavaScript deterministic object stringify

Check it out :)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
stamat
  • 1,832
  • 21
  • 26
0

JavaScript's built-in objects do provide hashtable functionality ...

Yes, kind of. Objects are by definition just a collection of key-value pairs (whereas keys are strings or Symbols). However as they are used like lookup tables by a lot of people, most engines will choose a hashtable like datastructure for implementing objects internally (in some cases). So yes, you can use objects as a hashtable (but it is not guaranteed that the engine will actually use a hashtable under the hood).

your keys are strings or numbers ...

No. Numbers will be casted to string when used as object keys, just as any other value. Therefore you can use Objects as a hashtable for numbers, and for strings, but not for both at the same time:

  { "1": true }[1] // true

If you need a Hashtable for arbitrary / mixed values, a Map or a Set will be the better choice, especially because they guarantee O(1) lookup time.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
0

Yes, keys can be numbers. In fact, the specification utilizes the same generic mapping functions for both Objects and Arrays.

John Green
  • 13,241
  • 3
  • 29
  • 51