Wednesday, November 13, 2013

JavaScript: Working with Arrays

I was going to include this material with the last post on jQuery but this material really deserves it's own attention. Many of us .NET developers are hesitant about JavaScript, and for good reason, it's a pain in the ass. But, as with all things, it is a mater of practice, practice, practice. This post will present working with JavaScript arrays and some of the slight differences between them. I'm running this in Visual Studio 2012 with Chrome.

To get rolling, we will declare some arrays to hold the data, some variables, and pass the variables to a function. The function will create a key that will be looked up in the cache and add if the key is not present.
var productCache = [];
var productCache2 = [];

var value1 = 1;
var value2 = 2;
var value3 = 3;

addToCache(value1);
addToCache(value2);
addToCache(value3);

function addToCache(val) {
   var id = "productId";
   var value = val;
   var key = id + "-" + value;

   productCache[key] = value;
   productCache2.push(value);
}
As displayed above, there is two ways to shuttle data into an array. You can use an index, or use the push function. Now, let's call the length property on each array and see what we have, afterall, using the length property is a pretty common scenario.
console.log(productCache.length);
console.log(productCache2.length);

Result: 0
Result: 3
How about that. Can that really be? Let's take a look at the objects with the developer tools.

 
If you read the post on jQuery tips you would know why this is happening. Indexes, by default, are numeric. In the first cache, we are using strings for indexes, this is why we are seeing nothing when length is called and when we peek into the object. Rest assured, the data is in there, that is what we will look at next.
Now we will look at iterating over these arrays, how to get the keys, how to get the values. You can accomplish this several different ways. This part may be boring but as developers, we like our options don't we? We will iterate the following ways: plain for loop, hasOwnProperty, and using ECMA 5. Both of the arrays will produce the same value and the technique is the same.
// Get keys with plain for loop
for (var key in productCache) {
   console.log("logging key: " + key);
}

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

// Get keys using hasOwnProperty
// hasOwnProperty will inspect properties that have been added, 'direct properties'
// it will not inspect properties inherited from Object through the prototype chain
for (var key in productCache) {
   if (productCache.hasOwnProperty(key)) {
      console.log("logging key: " + key);
   }
}

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

// Get keys using ECMA 5
var keys = Object.keys(productCache).map(function(key) {
   console.log("logging key: " + key;
   return key;
});

console.log(keys);

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

["productId-1", "productId-2", "productId-3"]

// Get values with plain for loop
for (var key in productCache) {
   var value = productCache[key];
   console.log("logging value: " + value);
}

Result:
logging value: 1
logging value: 2
logging value: 3

// Get values using hasOwnProperty
// hasOwnProperty will inspect properties that have been added, 'direct properties'
// it will not inspect properties inherited from Object through the prototype chain
for (var key in productCache) {
   if (productCache.hasOwnProperty(key)) {
      var value = productCache[key];
      console.log("logging value: " + value;
   }
}

Result:
logging value: 1
logging value: 2
logging value: 3

// Get values using ECMA 5
var values = Object.keys(productCache).map(function(key) {
   console.log("logging value: " + productCache[key]);
   return productCache[key];
});

console.log(values)

Result:
logging value: 1
logging value: 2
logging value: 3

[1, 2, 3]
Deleting from an array, you have to be careful when you are doing this. Depending on how you are storing items in the array and which way you choose to delete an item, can have some unexpected side effects. Just to recap and try to readjust our brains a little bit, we have two arrays, and this is what they look like:

productCache:
[productId-1] = 1
[productId-2] = 2
[productId-3] = 3

productCache2:
0: 1
1: 2
2: 3

Let's start deleting stuff and see what goes boom. To delete an item, you use the, well, delete keyword.
for (var index in productCache) {
   var item = productCache[index];
   if (item == "3") {
      delete productCache[index];
   }
}
Before showing the results, I'd like to call attention to something. Typically, when we deal with indexes we think of them as a number when we are iterating. But when you step outside of a language that possess the awesomeness of C#, you can't be guaranteed anything. Anyway, recall that in this array, we are storing key/value pairs and in our case, the key is a string, not a number. Above, when we are iterating through this array, the first time through, index is "productId-1", second time through, "productId-2", third time, "productId-3". Let's have a look at the results....

 
Cool, it worked, no surprises. Let's move on to the other array. Since we just pushed values into this array, it will have the normal indexes of 0, 1, and 2.
for (var index in productCache2) {
   var item = productCache2[index];
   if (item == "3") {
      delete productCache2[index];
   }
}
 
 
 
Uh oh indeed. See what happened there? The item did in fact get removed but the length did not change! Is there something wrong with our eyes??? Let's investigate a bit more...

 
Yes, that is the expected behavior when using delete with arrays. That "undefined x 1" will be put in the array for any item you delete. The alternative, is to use the splice method. The first parameter that splice takes is the index, the second parameter is a number indicating how many items to cleave.
for (var index in productCache2) {
   var item = productCache2[index];
   if (item == "3") {
      productCache2.splice(index, 1);
   }
}
 
 
 
 
Splice with not work with our other array. This is rather simple, the first parameter to splice expects a number. That array uses strings for keys.