Chapter 5. Working with Arrays and Loops

5.0. Introduction

An array is an ordered collection of elements. In JavaScript, an array can be created using formal object notation, or it can be initialized using literal notation, as demonstrated in the following code:

var arrObject = new Array("val1", "val2"); // array as object
var arrLiteral = ["val1", "val2"]; // array literal

To the developer, there is no difference: you can invoke an Array method on both a literal and an object. However, to the JavaScript engine, an array literal has to be reinterpreted each time it’s accessed, especially when used in a function call. On the positive side, though, array literals can replace the need for temporary variables, especially when sending values to a function.

A new Array object is created using the new operator, as follows:

var arrObject = new Array();

You can also create a new array that has some values:

var arrObject = new Array("val1","val2");

You can create an array literal by using square brackets to hold the array values. For instance, you can define an array literal and assign it to a variable:

var arrLiteral = ["val1","val2","val3"];

You can also create, and use, a literal array in a function or method call:

someFunction("param1", ["val1","val2"]);

Note, though, that when you pass a variable containing an array literal to a function, it is passed by reference—the same as passing a variable holding an Array object. Changes to the variable in the function are reflected outside of the function:

function chgArray(arr) {
   arr[0] = "surprise!";
}

var newArray = new Array("val1", "val2");
var newLiteral = ["val1","val2"];

chgArray(newArray);
chgArray(newLiteral);

alert(newArray); // prints surprise!,val2
alert(newLiteral); // prints surprise!,val2

An array, whether literal or object, can hold values of different data types:

var arrObject = new Array("val1", 34, true); // string, number, boolean
var arrLiteral = [arrObject, "val2", 18, false); // object, string, number, boolean

You can print out an array; the JavaScript engine will automatically convert the array into a string representation:

alert(arrLiteral); // prints out val1,34,true,val2,18,false

In this example, the JavaScript engine makes the array-to-string conversion for both the array literal and the array object contained as an element within the array literal.

Array elements can be accessed directly, using square brackets containing their index (position in the array). In addition, array elements can be set using the same index, which automatically creates the array element if it doesn’t exist:

var arrObject = new Array();

arrObject[0] = "cat"; // array now has one element
alert(arrObject[0]); // prints cat

Arrays in JavaScript are zero-based, which means the first element index is zero, and the last element is at the array length, minus 1:

var farmAnimals = new Array("cat","dog","horse","pig");
alert(farmAnimals[0]); // print cat
alert(farmAnimals[3]); // print pig

Not all array elements have to be defined when created. For instance, if you create an array literal, you can use commas to delimit array elements that don’t yet exist:

var arrLiteral = ["val1",,"val3"];

In this code, the second array element is currently undefined. You can’t use the empty comma, though, to add an undefined array element to the end of the array: JavaScript will just ignore it.

To create an array of several undefined elements, you can provide an array length when creating an array:

var largeCollection = new Array(100); // a new array with 100 undefined elements

One you’ve created an array, using Array object or literal notation, you can access the array elements in a loop, or use any number of array methods.

5.1. Looping Through an Array

Problem

You want to easily access all elements of an array.

Solution

The most common approach to accessing an array is to use a for loop:

var mammals = new Array("cat","dog","human","whale","seal");
var animalString = "";
for (var i = 0; i < mammals. length; i++) {
   animalString += mammals[i] + " ";
}
alert(animalString);

Discussion

A for loop can be used to access every element of an array. The array begins at zero, and the array property length is used to set the loop end.

Sometimes, though, you don’t want to access every element of the array. For instance, you might want to traverse an array until you find either a specific element, or any element that meets (or doesn’t meet) a certain criteria. In these cases, you’ll want to use a while loop and test the array elements:

var numArray = new Array(1,4,66,123,240,444,555);
var i = 0;

while (numArray[i] < 100) {
     alert(numArray[i++]);
}

Notice that the index counter, i, is incremented as it’s used to access an array element. The use of i++ means that the existing value of i is accessed first, and then the variable is incremented.

5.2. Creating a Multidimensional Array

Problem

You want to create a multidimensional array (an array of arrays).

Solution

Create an array in which each element is also an array. For example, to create an array with three elements, each of which is also an array of three elements containing, respectively, string, number, and array literals, use the code snippet in Example 5-1.

Example 5-1. Creating a multidimensional array
// set array length
var arrayLength = 3;

// create array
var multiArray = new Array(arrayLength);
for (var i = 0; i < multiArray.length; i++) {
  multiArray[i] = new Array(arrayLength);
}

// add items to first array index
multiArray[0][0] = "apple";
multiArray[0][1] = "banana";
multiArray[0][2] = "cherry";

// second
multiArray[1][0] = 2;
multiArray[1][1] = 56;
multiArray[1][2] = 83;

// third
multiArray[2][0] = ['test','again'];
multiArray[2][1] = ['Java','script'];
multiArray[2][2] = ['read','books'];

alert(multiArray); // printed out in first index order
alert(multiArray[2]); // prints out subarray
alert(multiArray[2][2][0]); // individual item

Discussion

Multidimensional arrays in JavaScript are managed by creating a new array as an element within an existing array. The new array can be created as an Array element, or as an array literal.

In Example 5-1, an array, multiArray, is created as an Array object with three members. Each of those three elements is also created as Array objects with three members. The array data is then set, with the first array member containing string literals, the second containing number literals, and the third array literals—themselves containing two array members, each with a string literal.

To access the array elements, use the square bracket notation, with each set of brackets used to address each level of the array. In the following code, the array contents are printed out via an alert window, after being converted to a string first, if necessary:

alert(multiArray[2]); // prints out test,again,Java,script,read,books
alert(multiArray[2][2]); // prints out read,books
alert(multiArray[2][2][1]); // prints out books

Multidimensional arrays are typically used to hold the data from a table structure, but how the structure is maintained is up to the developer. For instance, the developer can support an array structure in which the outer index reflects the columns, and the inner reflects the rows. As an example, Table 5-1 shows a simple five-column, three-row table containing a set of numbers.

Table 5-1. Simple table with five columns and three rows and sample data

45.89

4

34

9998.99

56

3

23

99

43

2

1

1

0

43

67

To create this in JavaScript using a multidimensional array, use the following code:

var table = new Array(5);

table[0] = [45.89, 4, 34, 9998.99, 56]; // first row
table[1] = [3, 23, 99, 43, 2]; // second row
table[2] = [1, 1, 0, 43, 67]; // third row

Of course, this doesn’t take into account column and row headers. To add in the headers, just treat them as array data, making sure to incorporate them into the proper place in the array to reflect the table structure.

Note

In a multideveloper environment, it’s essential that there is agreement among the developers about whether table structure data is stored column-centric or row-centric.

5.3. Creating a String from an Array

Problem

You want to create a single string from an array.

Solution

Use the Array object’s built-in join method to join the array elements into a string:

var fruitArray = ['apple','peach','lemon','lime'];
var resultString = fruitArray.join('-'); // apple-peach-lemon-lime

Discussion

The Array join method takes one optional parameter, a delimiter used to separate the strings when joined—in this case, the dash (-). It returns a string with all of the array elements concatenated. If the array contains anything other than strings, the values are converted to a string equivalent:

var numberArray = [1,2,3,4,5]; // array literal containing number elements
var resultString = numberArray.join('+'); // returns string with 1+2+3+4+5

If the delimiter parameter isn’t provided, a comma is inserted between array element values by default:

var numberArray = [1,2,3,4,5];
var resultString = numberArray.join(); // returns string with 1,2,3,4,5

5.4. Sorting an Array

Problem

You want to sort an array.

Solution

Use the Array object’s sort method:

var fruitArray = ['strawberry','apple','orange','banana','lime'];
alert(fruitArray.sort()); // returns apple,banana,lime,orange,strawberry

Discussion

The Array object’s sort method sorts the array elements alphabetically if no optional compare function parameter is provided. To facilitate the sort, all data types are converted to their string equivalent before sorting:

var numberArray = [4,13,2,31,5];
alert(numberArray.sort()); // returns  13,2,31,4,5

Though the array members in this example are numbers, they’re sorted in lexicographical (dictionary) order, not numerically. To do an actual numeric sort, use a custom sort function:

function compareNumbers(a,b) {
   return a - b;
}
var numArray = [13,2,31,4,5];
alert(numArray.sort(compareNumbers)); // prints 2,4,5,13,31

The function subtracts the second parameter value from the first, and if the first is less than the second, a negative value is returned; otherwise, the value is positive. If the return value is less than zero, the sort index for the second parameter is set higher than the first parameter. If the value is greater than zero, the sort index for the first parameter is set higher than the other. If the value is exactly zero, the sort index for the two is unchanged.

If the array elements contain strings that could be converted to numbers, then the compareNumbers sort function still works, as number conversion is automatic:

var numberArray=["34","4","5"];
alert(numberArray.sort(compareNumbers)); // prints 4,5,34

The sort method sorts the elements in an ascending order. If you want to do a reverse sort, use the sort method to sort the elements, and then use the reverse method to reverse the array member order:

var numberArray = [4,5,1,3,2];
numberArray.sort();
numberArray.reverse(); // array now has 5,4,3,2,1

5.5. Store and Access Values in Order

Problem

You want to store values in such a way that you can access the values in the order in which they were stored.

Solution

To store and access values in the order in which they’re received, create a FIFO (first-in, first-out) queue. Use the JavaScript Array object push method to add items to the queue, and shift to retrieve:

// create new array
var queue = new Array();

// push on three entries
queue.push('first');
queue.push('second');
queue.push('third');

// shift two entries
alert(queue.shift());  // returns first
alert(queue.shift());  // returns second
alert(queue);  // returns third

Discussion

A queue is an array of elements that are added one at a time, and retrieved in a first-in, first-out order (FIFO). Think of a line at the bank: people go to the end when they arrive at the bank, and tellers help those in the front of the line, who have been there the longest.

You could emulate this behavior using counter variables to hold the index of the last item added (the end), and the index of the last one retrieved (from the front), but luckily, the JavaScript Array object provides methods that handle this information for us, and also keep the array clean in the process.

The Array push method creates a new array element and adds it to the end of the array:

queue.push('first');

The array element count increments with each pushed element.

The Array shift method extracts the array element from the front of the array, removing it from the array, and returning the element:

var elem = queue.shift();

The array element count decreases by one with each shifted element, as shift also modifies the array in addition to returning the item.

5.6. Store and Access Values in Reverse Order

Problem

You want to store values in such a way that you can access the values in reverse order: access the most recently stored value first, then a LIFO (last-in, first-out) stack.

Solution

To access stored values in reverse order (last item added is accessed first), create a LIFO (last-in, first-out) stack. Use the JavaScript Array object’s push method to add items to the stack, and the pop method to retrieve:

// create new array
var queue = new Array();

// push on three entries
queue.push('first');
queue.push('second');
queue.push('third');

// pop two entries
alert(queue.pop());  // returns third
alert(queue.pop());  // returns second
alert(queue); // returns first

Discussion

A stack is an array of elements, with each new element added to the top of the stack, and retrieved in a last-in, first-out (LIFO) order. Think of a stack of dishes: you add plates to the top as they’re washed, and retrieve them from the top when needed. You could use a variable holding an integer that tracks the end of the array after each addition and retrieval, but JavaScript provides the functionality we need.

The Array push method creates a new array element and adds it to the end of the array:

queue.push('first');

The array element count increments with each pushed element.

The Array pop method extracts the array element from the end of the array, removing it from the array, and returning the element:

var elem = queue.pop();

The array element count decreases by one with each popped element, as pop modifies the array.

5.7. Create a New Array as a Subset of an Existing Array

Problem

You want to create a new array from a segment of an existing array. If the array elements are objects, you want to keep both arrays in sync.

Solution

Use the Array object slice method to create a new array based on elements within a given range:

var origArray = new Array(4);
origArray[0] = new Array("one","two");
origArray[1] = new Array("three","four");
origArray[2] = new Array("five","six");
origArray[3] = new Array("seven","eight");

// create new array using slice
var newArray = origArray.slice(1,3);

Discussion

The Array slice method is a simple way of building a new array from a consecutive sequence of elements in another array. The parameters are the beginning and ending index for the sequence of elements to copy. A negative value for either index indicates that slice should work from the end of the array.

If the copied elements are literal values, such as strings, numbers, and Booleans, they’re copied by value—changing the value in the old array has no impact on the same values in the new array, and vice versa.

When objects are copied, though, they’re copied by reference, whether they’re copied via slice or by direct variable assignment:

var first = new Array("one","two","three");
var second = first; // copied by reference
second[1] = "apple"; // first and second arrays now have "one","apple","three"

The code that follows demonstrates the object syncing when used with slice. A section of one array is used to create a new array with slice. The elements in the first array are Array objects. In the code, when the value of one of the objects in the first array is changed, the change is reflected in the new array. Conversely, when a value is changed in the new array, the change is reflected in the original array:

var origArray = new Array(4);
origArray[0] = new Array("one","two");
origArray[1] = new Array("three","four");
origArray[2] = new Array("five","six");
origArray[3] = new Array("seven","eight");

var newArray = origArray.slice(1,3);
alert(newArray); // prints out three,four,five,six

// modify original
origArray[1][0] = "octopus";

// print out new
alert(newArray); // prints out octopus,four,five,six

// modify new
newArray[1][1] = "kitten";

// print out old
alert(origArray); // prints out one,two,octopus,four,five,kitten,seven,eight

Another handy use for slice is to convert the function arguments property into a proper array:

var args = Array.prototype.slice.call(arguments);

Using slice to create a subset of an array is a way of quickly copying a subset of an array and, if the values are objects, ensure both arrays are in sync. Be aware, though, that IE8 doesn’t support slice.

5.8. Searching Through an Array

Problem

You want to search an array for a specific value and get the array element index if found.

Solution

Use the new (ECMAScript 5) Array object methods indexOf and lastIndexOf:

var animals = new Array("dog","cat","seal","elephant","walrus","lion");
alert(animals.indexOf("elephant")); // prints 3

Discussion

Though support for both indexOf and lastIndexOf has existed in browsers for some time, it’s only been formalized with the release of ECMAScript 5. Both methods take a search value, which is then compared to every element in the array. If the value is found, both return an index representing the array element. If the value is not found, –1 is returned. The indexOf method returns the first one found, the lastIndexOf returns the last one found:

var animals = new Array("dog","cat","seal","walrus","lion", "cat");

alert(animals.indexOf("cat")); // prints 1
alert(animals.lastIndexOf("cat")); // prints 5

Both methods can take a starting index, setting where the search is going to start:

var animals = new Array("dog","cat","seal","walrus","lion", "cat");

alert(animals.indexOf("cat",2)); // prints 5
alert(animals.lastIndexOf("cat",4)); // prints 1

Currently, all of the book’s target browsers support indexOf and lastIndexOf, except for IE8.

See Also

As mentioned, not all browsers support indexof and lastIndexOf. A cross-browser method to implement like functionality in these browsers is given in the Mozilla documentation, at https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/indexOf. Since IE8 doesn’t support indexOf, here’s the Mozilla workaround for the function:

if (!Array.prototype.indexOf)
{
  Array.prototype.indexOf = function(elt /*, from*/)
  {
    var len = this.length >>> 0;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}

5.9. Flatten a Multidimensional Array

Problem

You want to flatten a multidimensional array into a single dimensional array.

Solution

Use the Array object concat method to merge the array dimensions into a single dimensional array:

var origArray = new Array();
origArray[0] = new Array("one","two");
origArray[1] = new Array("three","four");
origArray[2] = new Array("five","six");
origArray[3] = new Array("seven","eight");

// flatten array
var newArray = origArray[0].concat(origArray[1],origArray[2],origArray[3]);
alert(newArray[5]); // prints six

Discussion

The Array object concat method takes one or more arrays, and appends the array elements on to the end of the contents of the parent array on which the method was called. The merged array is then returned as a new array.

One use for this type of functionality is to return a single dimensional array made up of elements from a multidimensional array, as shown in the solution.

5.10. Search and Remove or Replace Array Elements

Problem

You want to find occurrences of a given value in an array, and either remove the element or replace with another value.

Solution

Use the Array methods indexOf and splice to find and remove/replace array elements:

var animals = new Array("dog","cat","seal","walrus","lion", "cat");

// remove the element from array
animals.splice(animals.indexOf("walrus"),1); // dog,cat,seal,lion,cat

// splice in new element
animals.splice(animals.lastIndexOf("cat"),1,"monkey"); // dog,cat,seal,lion,monkey

Discussion

The splice method takes three parameters. The first parameter is required; it’s the index where the splicing is to take place. The other two parameters are optional: the number of elements to remove, and a substitute. If the index is negative, the elements will be spliced from the end, not from the beginning of the array:

var animals = new Array("cat","walrus","lion", "cat");

// splice in new element
animals.splice(-1,1,"monkey"); // cat,walrus,lion,monkey

If the number of elements to splice is not provided, all elements from the index to the end will be removed:

var animals = new Array("cat","walrus","lion", "cat");

// remove all elements after second
animals.splice(2); // cat,walrus

The last parameter, the replaced value, can be a set of replacement values, separated by commas:

var animals = new Array("cat","walrus","lion", "cat");

// replace second element with two
animals.splice(2,1,"zebra","elephant"); // cat,walrus,zebra,elephant,cat

Removing or replacing one element is handy, but being able to remove or replace all instances of a particular element is even handier. In Example 5-2, an array is created with several elements, including multiple instances of a specific value. The splice method is then used in a loop to replace all of the elements with this one value with elements with a new value. The splice method is used again, in a separate loop, to remove the newly spliced elements.

Example 5-2. Using looping and splice to replace and remove elements
<!DOCTYPE html>
<head>
<title>Looping and Splicing</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<script>

var charSets = new Array("ab","bb","cd","ab","cc","ab","dd","ab");

// replace element
while (charSets.indexOf("ab") != -1) {
   charSets.splice(charSets.indexOf("ab"),1,"**");
}
alert(charSets); // **,bb,cd,**,cc,dd,**

// delete new element
while(charSets.indexOf("**") != -1) {
   charSets.splice(charSets.indexOf("**"),1);
}
alert(charSets); // bb,cd,cc,dd

</script>

</head>
<body>
</body>

The example works with all of this book’s target browsers except for IE8, which doesn’t currently support either indexOf or splice.

See Also

See Recipe 5.8 for a workaround for indexOf.

5.11. Applying a Function Against Each Array Element

Problem

You want to use a function to check an array value, and replace it if it matches a given criterion.

Solution

Use the new ECMAScript 5 Array object forEach to attach a callback function to each array element:

var charSets = new Array("ab","bb","cd","ab","cc","ab","dd","ab");

function replaceElement(element,index,array) {
   if (element == "ab") array[index] = "**";

}

// apply function to each array element
charSets.forEach(replaceElement);
alert(charSets); // prints **,bb,cd,**,cc,**,dd,**

Discussion

In the last section, we used a while loop to traverse an array to find and replace a value, but how much more helpful is it to use the forEach method?

The forEach method takes one parameter, the function. The function itself has three parameters: the array element, the index of the element, and the array. All three were used in the function, replaceElement.

First, the element’s value is tested to see if it matches a given string, ab. If matched, the array element’s index is used to modify the array element’s value with the replacement string, **.

Note

Don’t return a value from the function passed to the forEach method, as the value will be discarded.

Chrome, Firefox, Opera, and Safari support forEach, but IE8 does not.

See Also

The concept of callback functions is covered in more detail in Chapter 6.

Most modern browsers support forEach. However, for those that don’t, you can emulate the forEach behavior using the Array.prototype property. Mozilla provides a description about how to emulate forEach at https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach. For completeness, I’ve duplicated the code below. To use, add the code into a library function and make sure it’s processed before the forEach method is needed:

if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}

5.12. Applying a Function to Every Element in an Array and Returning a New Array

Problem

You want to convert an array of decimal numbers into a new array with their hexadecimal equivalents.

Solution

Use the Array object map method to create a new array consisting of elements from the old array that have been modified via a callback function passed to the map method:

// function to convert decimal to hexadecimal
function convertToHex(element,index,array) {
   return element.toString(16);
}

var decArray = new Array(23, 255, 122, 5, 16, 99);

var hexArray = decArray.map(convertToHex);
alert(hexArray); // 17,ff,a,5,10,63

Discussion

Like the forEach method in Recipe 5.11, the ECMAScript 5 map method allows us to attach a callback function that is applied to each array element. Unlike forEach, though, the map method results in a new array rather than modifying the original array. Therefore, you won’t return a value when using forEach, but you must return a value when using map.

The function that’s passed to the map method has three parameters: the current array element, the index for the array element, and the array. The forEach and map methods are currently not supported by IE8.

See Also

Most modern browsers support the Array object map method, but to ensure that the functionality is present, you can use the Array.prototype property to emulate the method’s behavior. See how at the Mozilla website.

For comprehensiveness, I’ve included the code for the workaround below. To use, include the code in a library function that is processed before the map method is needed:

if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

5.13. Creating a Filtered Array

Problem

You want to filter element values in an array and assign the results to a new array.

Solution

Use the Array object filter method:

function removeChars(element,index,array) {
   return (element !== "**");
}

var charSet = new Array("**","bb","cd","**","cc","**","dd","**");

var newArray = charSet.filter(removeChars);
alert(newArray); // bb,cd,cc,dd

Discussion

The filter method is another ECMAScript 5 addition, like forEach and map, covered in Recipes 5.11 and 5.12, respectively. Like them, the method is a way of applying a callback function to every array element.

The function passed as parameter to the filter method returns a Boolean value, true or false, based on some test against the array elements. This returned value determines if the array element is added to a new array: it is added if the function returns true; otherwise, it is not added. In the solution, the character string “**” is filtered from the original array when the new array is created.

The function has three parameters: the array element, the index for the element, and the array, itself. The filter method is not supported by IE8.

See Also

Support for filter is fairly broad, but to ensure access to the functionality, there is a way to emulate the filter method using Array.prototype. Mozilla details the approach at https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter, but I’ve copied the technique below. To use, include the function in your code, and run the function before you need to access the filter method:

if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}

5.14. Validating Array Contents

Problem

You want to ensure that an array meets certain criteria.

Solution

Use the Array object’s every method to check that every element passes a given criteria. For instance, the following code checks to ensure that every element in the array is an alphanumeric character:

var elemSet = new Array("**",123,"aaa","abc","-",46,"AAA");

// testing function
function textValue (element,index,array) {
   var textExp = /^[a-zA-Z]+$/;
   return textExp.test(element);
}

// run test
alert(elemSet.every(textValue)); // false

Or use the Array object’s some method to ensure that at least some of the elements pass the criteria. As an example, the following code checks to ensure that at least some of the array elements are alphanumeric strings:

var elemSet = new Array("**",123,"aaa","abc","-",46,"AAA");

// testing function
function textValue (element,index,array) {
   var textExp = /^[a-zA-Z]+$/;
   return textExp.test(element);
}

// run test
alert(elemSet.some(textValue)); // true

Discussion

The every and some Array object methods are the last of the ECMAScript 5 Array methods I’ll be covering in this book. Unlike the Array callback function methods I covered in previous recipes in this chapter, every and some functions do not work against all array elements; they only process as many array elements as necessary to fulfill their functionality.

The solution demonstrates that the same callback function can be used for both the every and the some Array object methods. The difference is that when using the every method, as soon as the function returns a false value, the processing is finished, and the method returns false. The some method, though, will continue to test against every array element until the callback function returns true. At that time, no other elements are validated, and the method returns true. However, if the callback function tests against all elements, and doesn’t return true at any point, the some method returns false.

Which method to use depends on your needs. If all array elements must meet certain criteria, then use every; otherwise, use some.

The callback function takes three parameters: the element, the index for the element, and the array. Neither the some or every method are supported by IE8, but they are supported by the other target browsers for this book.

See Also

Most modern browsers support every and some, but for those browsers that don’t (such as most versions of Internet Explorer), you can emulate the behavior using the Array.prototype. Mozilla covers how to do this at https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/some and https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/every.

For comprehensiveness, I’ve also included the functionality below. To use, ensure that the script provided is processed before the methods are needed.

Here’s how to emulate some:

if (!Array.prototype.some)
{
  Array.prototype.some = function(fun /*, thisp*/)
  {
    var i = 0,
        len = this.length >>> 0;

    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (; i < len; i++)
    {
      if (i in this &&
          fun.call(thisp, this[i], i, this))
        return true;
    }

    return false;
  };
}

Here’s how to emulate every:

if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}

5.15. Using an Associative Array to Store Form Element Names and Values

Problem

You want to store form element names and values, for later validation purposes.

Solution

Use an associative array to store the elements, using the element identifiers as array index:

var elemArray = new Object(); // notice Object, no Array
var elem = document.forms[0].elements[0];
elemArray[elem.id] = elem.value;

Iterate over the array using the for...in statement:

for (var key in elemArray) {
  str+=key + "," + elemArray[key] + " ";
}

Discussion

Most JavaScript arrays use a numeric index, such as the following:

arr[0] = value;

However, you can create an associative array in JavaScript, where the array index can be a string representing a keyword, mapping that string to a given value. In the solution, the array index is the identifier given the array element, and the actual array value is the form element value.

You can create an associative array, but you’re not using the Array object to do so. Using the Array object is risky and actively discouraged—especially if you’re using one of the built-in libraries that use the prototype attribute for extending objects, as people discovered when the popular Prototype.js library was first released several years ago.

The earlier Prototype.js library made an assumption that most array use in JavaScript is numeric index–based, like most of the earlier examples in this chapter. The library extended the Array object functionality via Array.prototype, based on this assumption. But extending Array objects in this way breaks the for...in loop functionality used to traverse an associative array created from an Array object.

It’s not that Prototype.js was “breaking” JavaScript. The for...in loop was intended for one purpose: iterating over an object’s properties, such as being able to loop through the String object’s properties, or your own custom object properties.

When we use an Array object to create an associative array, what we’re really doing is adding new properties to the array object, rather than adding new array elements. You could actually create an associative array with a RegExp or String, as well as an Array. The reason is that in JavaScript objects are associative arrays. When you’re adding a new array, element:

obj[propName] = "somevalue";

what you’re really doing is adding a new object property:

obj.propName = "somevalue";

To further demonstrate how different the associative array is from a numeric-based array, when you use an Array to create an associative array, you can’t access the array “elements” by index, and the length property returns zero.

Instead of using an Array object to create the associative array, use the JavaScript Object directly. You get the exact same functionality, but avoid the clashes with libraries that extend the base Array object using prototype.

Example 5-3 shows a web page. Here, when the form is submitted, all of the form elements of type text are accessed and stored in an associative array. The element IDs are used as the array keyword, and the values assigned to the array elements. Once collected, the associative array is passed to another function that could be used to validate the values, but in this case just creates a string of keyword/value pairs, which is then displayed.

Example 5-3. Demonstrating associative array with form elements
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Associative Array</title>
<script type="text/javascript">
//<![CDATA[

// get the form element names and values
function getVals() {
  var elems = document.getElementById("picker").elements;
  var elemArray = new Object();
  for (var i = 0; i < elems.length; i++) {
    if (elems[i].type == "text")
       elemArray[elems[i].id] = elems[i].value;
  }
  checkVals(elemArray);
  return false;
}

// check values
function checkVals(elemArray) {

  var str = "";
  for (var key in elemArray) {
    str+=key + "," + elemArray[key] + " ";
  }

 document.getElementById("result").innerHTML = str;
}

//--><!]]>
</script>
</head>
<body>
<form id="picker" onsubmit="return getVals()">
<label>Value 1:</label> <input type="text" id="first" /><br />
<label>Value 2:</label> <input type="text" id="second" /><br />
<label>Value 3:</label> <input type="text" id="third"  /><br />
<label>Value 4:</label> <input type="text" id="four"  /><br />
<input type="submit" value="Validate" />
</form>
<div id="result"></div>
</body>
</html>

In the example, notice that the array index is formed by the form element’s id. When the array is traversed, the for loop syntax used is:

for (keyword in array)

This syntax accesses the array index, which is then assigned to the keyword variable that can be used to access the array value:

for (keyword in array)
   var a = array[keyword];

Figure 5-1 shows the example after values are typed into the form fields and the form is submitted.

Demonstration of associative array and traversing form elements
Figure 5-1. Demonstration of associative array and traversing form elements

This type of keyword/value pairing is commonly referred to as a hash map or hash table, though the JavaScript functionality isn’t a true hash map functionality. The reason why it isn’t a true hash map is that it doesn’t account for the fact that the same keyword could be used with multiple values, and the JavaScript version only accepts strings as keywords.

See Also

See Chapter 16 for more on the object nature of JavaScript, and Recipe 16.3 for more information about extending the built-in objects, such as Array, using the prototype property. For more on the risks associated with associative arrays in JavaScript, read “JavaScript ‘Associative Arrays’ Considered Harmful”, by Andrew Dupont.

Get JavaScript Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.