Introduction
Hash tables are a permutation of associative arrays (i.e. name => value pairs). If you use PHP, then you are very familiar with this type of data structure already since all PHP arrays are associative.
The Javascript language implements very loose and somewhat limited support for associative arrays. Any JavaScript array can use other objects as keys, making it a hash, but there exists no formal constructor for initializing them and it is more or less unweildy to work with. A short example of a hash in JavaScript would be as follows:
var myArray = new Array();
myArray['one'] = 1;
myArray['two'] = 2;
myArray['three'] = 3;
// show the values stored
for (var i in myArray) {
alert('key is: ' + i + ', value is: ' + myArray[i]);
}
Just as in PHP, the 'foreach' contruct is used to run through the array, doing something for each key => value pair. However, notice I did not do:
for (var i = 0; i < myArray.length; i++) {
alert('key is: ' + i + ', value is: ' + myArray[i]);
}
This approach would not work in Javascript because the length property of an associative array in JavaScript is not incremented for arrays with non-numeric keys and must otherwise be explicitly assigned. Part of the reason for this goes back to the missing constructor for created associative arrays.
Fundamentals
After a brief discussion of fundamentals we will begin to focus on the core problem. In JavaScript, every variable is in fact an object. Okay, so what does this mean? Well, essentially, no matter what the variable, it can be used as though it were an instance of an object. This means it has a constructor, methods and properties. A property is just a variable that is owned by the object and thus local to that object. A property is accessed using the syntax:
myArray.one
where one is the property and the '.' symbol signifies we are talking about the property of the array (or object) myArray. So the above example could be alternatively executed as:
for (var i in myArray) {
alert('key is: ' + i + ', value is: ' + eval('myArray.' + i));
}
NOTE: In this example we have to use the function 'eval()' because we want to contruct the call myArray.one dynamically.
Since each object has default properties that are accessed using this very same syntax, such as length and constructor, consider the case where the key in the associative array is the same as one of these properties. This situation highlights the fundamental problem with associative arrays in JavaScript. It should be clear now why the length property is not set when we make an associative array data structure. By creating an associative array, you wipe out the original structure of the object because it is no longer possible differentiate between data keys and object properties. They become one in the same.
As a matter of fact, this is not the only conflict. Each object has a property called contructor, which is the function reference which is used to contruct the object. Ahhh, so now we see why hashes in JavaScript have no contructor. By creating an associative array, it is possible to wipe out the constructor in the process. In fact, hashes in JavaScript are somewhat useless for anything more than a very simple, static array. You have to know what it will be used for before you use it. Okay, so enough run on, what are we going to do about it?
Constructing a Hash Class
Javascript is very nice language in the sense that we can create our own classes. So what we are going to do is create a Hash() class just like the Array() class, except we are going to get around the conflicts that we are running into with this Array() class.
You might thinking, "okay, so we make a class, but how do we get around the conflicting properties problem?". Easy, we make a property which itself is an array and call it items. Then, we can use any key we want, and store the data about the array in other properties. The trick is to move the data part of the array inside of a property of the class. The following listing is the Hash() object definition:
function Hash()
{
this.length = 0;
this.items = new Array();
for (var i = 0; i < arguments.length; i += 2) {
if (typeof(arguments[i + 1]) != 'undefined') {
this.items[arguments[i]] = arguments[i + 1];
this.length++;
}
}
}
NOTE: You should select another name for this class if you are using the prototype JavaScript library to avoid a naming conflict.
Let's break this down a bit. Right off the bat, we create a length property, which will just be 0 to start with. Additionally, we create our items array using the Array() contructor. Next we populate that array with the key => value pairs we passed in and continue to increment the length. Ah, but JavaScript doesn't know anything about the special syntax that say PHP uses. So we have to invent our own. What we will do is just alternate key and value arguments to the contructor (similar to Perl). A typical call to create a Hash() object would use the following syntax:
var myHash = new Hash('one', 1, 'two', 2, 'three', 3);
Already it must be nice to see a contructor...so much easier to add data to the structure!
Now, as you may recall before, we couldn't have any properties or methods in our associative array, so besides a 'foreach' construct, there was not much we could do with our associative array. Now that we have the ability to add methods and properties, let's get started! So we enhance our Hash() class.
function Hash()
{
this.length = 0;
this.items = new Array();
for (var i = 0; i < arguments.length; i += 2) {
if (typeof(arguments[i + 1]) != 'undefined') {
this.items[arguments[i]] = arguments[i + 1];
this.length++;
}
}
this.removeItem = function(in_key)
{
var tmp_previous;
if (typeof(this.items[in_key]) != 'undefined') {
this.length--;
var tmp_previous = this.items[in_key];
delete this.items[in_key];
}
return tmp_previous;
}
this.getItem = function(in_key) {
return this.items[in_key];
}
this.setItem = function(in_key, in_value)
{
var tmp_previous;
if (typeof(in_value) != 'undefined') {
if (typeof(this.items[in_key]) == 'undefined') {
this.length++;
}
else {
tmp_previous = this.items[in_key];
}
this.items[in_key] = in_value;
}
return tmp_previous;
}
this.hasItem = function(in_key)
{
return typeof(this.items[in_key]) != 'undefined';
}
this.clear = function()
{
for (var i in this.items) {
delete this.items[i];
}
this.length = 0;
}
}
Understanding the Implementation
We now have lots of useful methods! In JavaScript, any variable can be a reference to a function, so to add methods to the class, the easiest way to do it is to just write the function and then assign it to a property in the class. Okay, so you may be thinking, "But I can't have the same property name as a method name." That's right, another limitation of JavaScript objects is that methods are properties. However, in most cases, it won't be a problem because method names should be 'behavior' names and properties should be 'state' names.
In order to access the underlying items, we added the methods 'setItem', 'removeItem' and 'hasItem', and a 'clear' method to flush out the data. For now we will refer to each key => value pair as an item. We could create a 'getItem' method as well, but that is a bit slower and it doesn't server any real useful purpose. If you would like, add it for your own class...either way works. Note that if you are using the prototype JavaScript library, the 'hasItem' method will return values for any of the methods that prototype adds to Array. You will also need to ensure the item found is not a function.
The most important role of our methods is to keep the length property up to date. As we can see, it takes a lot of work out of our job and we can use these nice methods to work easily with our hash. Just like a Hash in Java, the return value is a reference to the item in the Hash() that was replaced:
alert("Previous value: " + myHash.setItem('foobar', 'hey'));
If you now want to iterate through the Hash() like we did the array in the very beginning, you may do so using two different approaches:
for (var i in myHash.items) {
alert('key is: ' + i + ', value is: ' + myHash.items[i]);
}
or
for (var i = 0; i < myHash.length; i++) {
alert('key is: ' + i + ', value is: ' + myHash.items[i]);
}
and if you made a method for 'getItem()', you could also do:
for (var i = 0; i < myHash.length; i++) {
alert('key is: ' + i + ', value is: ' + myHash.getItem(i));
}
If you are not concerned with speed down to the millisecond, then it is better to use the more formal approach of making a 'getItem()' method because it helps to prevent exposing the internal Array items.
Summary
Now you should go home and start using this in every JavaScript code you write because it finally makes Arrays in JavaScript useful. It is great for storing configuration data, returning multiple values from function, and the list goes on.
Now, I added a few more properties in some of my JavaScript programs to get more information out of the Hash() and so can you. In one application, I wanted to know how many integer keys the Hash() had rather than just the length of the whole Hash() and I wanted to be able to work with just the integer keys in one instance. You can do this by modifing the contrutor and methods to check for the type of the key using the function 'typeof()' and then setting the indexLength (as I called it) appropriately.