ECMAScript 5’s new array methods

Introduction

Modern web browsers have many new JavaScript features which often stay unknown for developers because they are accustomed to use some utility libraries or frameworks with adequate functionality. Tizen platform equipped with modern web runtime gives you a chance to use these new features without worrying about problem of cross-browser compatibility or efficiency. In this article we want to show you the new methods of iteration over an array which were introduced with ECMAScript 5.1.

Array traversing methods

The execution of iteration methods looks the same except reduce and reduceRight: there is some callback function which is called on every element of the array and optional context object for that callback. All methods except reduceRight loops through the array in ascending order.

traversedArray.method(callback [, thisObject])

Where:
callback - function which is called once on every array element, should accept three arguments
thisObject - optional, if provided it will be used as this value in callback function

function callback(element, index, traversedArray)

Where:
element - element of the array in the current iteration
index - position of the element in the array
traversedArray - reference to the array being iterated

What is returned depends on the specific method. The important difference between these methods and looping statements is skipping holes (gaps) in the array. Holes in the array can be created in the following ways:

  • using constructor new Array(n) - creates an array with length property equal to n and n holes in it
  • removing element using delete operator
  • leaving empty space between commas when array is literally defined

Array with holes is called sparse array, without them - dense array.

In the following example we created literally an array with two holes and six values, next we also deleted the first value. The length property before and after delete operation is equal to 8. Because of inconsistency between the real number of elements and length property it’s better to work with dense array. To avoid this don’t create array with holes and instead delete use splice to remove elements.

var letters = ['a','b',,'d','e','f',,'h'];
console.log(letters.length); // 8
delete letters[0];
console.log(letters.length); // 8

forEach - allows to iterate through all array elements, doesn’t return anything, basically it’s equivalent to for loop. In the following example we can see difference in iteration between forEach and for loop, forEach skips holes in the array (see logged indexes).

var indexes = [];
for(var i=0;i<letters.length;++i) {
    indexes.push(i);
}
console.log(indexes); // [0, 1, 2, 3, 4, 5, 6, 7]
indexes = [];
letters.forEach(function(el,i,a){
    indexes.push(i);		
});
console.log(indexes); // [1, 3, 4, 5, 7]

every - callback function is performed once on every array element until it returns false, then every also returns false, otherwise true.

some - callback function is performed once on every array element until it returns true, then also some returns true, otherwise false.

We can use some and every for checking if array match or doesn’t match some criteria.

We should use every when we are interested in an array with all elements passing test. In the following example we want to test array of points if it has points with both coordinates non-negative.

var points = [];
for(var i=0;i<10;++i) {
    points[i] = { x:Math.random()*20, y:-1+Math.random()*20 };
}
function hasNonNegativeCoords(el,i,a) {
    if(el.x>0 && el.y>0) {
        return true;
    } else {
        return false;
    }
}
var r = points.every(hasNonNegativeCoords);
console.log(r);

We should use some when we are interested in an array with any element passing test. In the following example we want to test array of points if it has any point which belongs to the first chart quarter (has both coordinates negative).

var points = [];
for(var i=0;i<10;++i) {
    points[i] = { x:-10+Math.random()*20, y:-10+Math.random()*20 };
}
function hasPointInQ1(el,i,a) {
    if(el.x<0 && el.y<0) {
        return true;
    } else {
        return false;
    }
}
var r = points.some(hasPointInQ1);

map - returns new array in which each element is returned by callback function, map doesn’t iterate over holes but preserves it in the new array.

In the following tricky snippet map is used to create dense array with three elements 0, 1, 2.

Array.apply(null, Array(3)).map(Function.call.bind(Number));

In the following example we want to find all the points below x axis and reflect them over this axis.

var points = [];
for(var i=0;i<10;++i) {
    points[i] = { x:-10+Math.random()*20, y:-10+Math.random()*20 };
}
function reflectNegativeOverXAxis(element, index, traversedArray) {
    if(element.y<0) {
        element.y = -element.y;
        return element;
    } else {
        return element;
    }
}
var r = points.map(reflectNegativeOverXAxis);	
console.log(r);

filter - returns new array with only these elements which pass test implemented by the callback function. When test fails for all elements empty array is returned. When callback function returns true element is included in the new array, if false it doesn’t.

In the following example we want to extract from an array of points some of them with y coordinate below test point.

var points = [];
for(var i=0;i<10;++i) {
    points[i] = { x:-10+Math.random()*20, y:-10+Math.random()*20 };
}
function findPointsBelow(point) {
    return function(element, index, traversedArray) {
        if(element.y<point.y) {
            return true;
        } else {
            return false;
        }
    }
}
var r = points.filter(findPointsBelow({x:0,y:0}));

In the following example filter is used to return elements starting with letter defined in context object called testFilter.

var words=['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];
var testFilter = {
    testChar: "s",
    charIndex: 0,
    testAction: function(element, index, traversedArray) {
        if(element.toLowerCase().charAt(this.charIndex)===this.testChar) {
            return true;
        } else {
            return false;
        }
    }
};
var r = words.filter(testFilter.testAction, testFilter);
console.log(r); // ["Saturday", "Sunday"] 

reduce(callback[, initialValue]) - reduces an array to a single value which is cumulative result of callback function invocation through all array elements. When initValue is passed to reduce function, iteration starts from index 0 and initValue is passed to callback as prevVal. Otherwise iteration starts from index 1 and prevVal is equal to the first element of the array (indexed with 0).

function callback(prevVal, currVal, index, traversedArray)

Where:
prevVal - value previously returned by callback, when initValue is defined it is equal to it otherwise it is undefined. It’s also called memo because it keeps value from previous function call
currVal - current element
index - current index
raversedArray - reference to the array

Basically we use reduce when we want to get result which is not a subset of the traversed array or new array. In such cases we could use map or filter functions. The return value of the reduce function can also be a complex object and thanks to that we can just create own implementations of filter and map functions using the reduce.

reduceRight(callback[, initialValue]) - is almost identical to reduce except that iteration is descending, starting from the last element of the array. Both reduce and reduceRight can be very useful when we want to search an array for some values and/or criteria and in result get some additional info (e.g. index, object properties, sum or difference, etc.).

In the following example we use reduce to count letters of each array element and return new array with number of letters for corresponding word.

var words=['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];    
function countLetters(prevVal,currVal,index,traversedArray) {
    var len = currVal.length;
    prevVal.push(len);
    return prevVal;
}
var r = words.reduce(countLetters,[]);
console.log(r); // [6, 7, 9, 8, 6, 8, 6]

In the following example we use reduce to add all numbers within an array including some initialValue.

var values=[1,3,4,5,8,10];    
function addNumbers(prevVal,currVal,index,traversedArray) {
    prevVal += currVal;
    return prevVal;
}
var r = values.reduce(addNumbers,-10);
console.log(r); // 21

In the following example we use reduceRight to return two points: top left and bottom right which can create rectangle covering all the points from the array:

var points = [];
for(var i=0;i<10;++i) {
    points[i] = { x:-10+Math.random()*20, y:-10+Math.random()*20 };
}
function findRectanglePoints(prevVal, currVal, index, traversedArray) {
    if(!prevVal.left) {
        prevVal = {left:{x:currVal.x,y:currVal.y}, right:{x:currVal.x,y:currVal.y}};
    } else {
        if(currVal.x < prevVal.left.x) {
            prevVal.left.x = currVal.x;
        } else if(currVal.x > prevVal.right.x) {
            prevVal.right.x = currVal.x;
        }
        if(currVal.y > prevVal.left.y) {
           prevVal.left.y = currVal.y;
        } else if(currVal.y < prevVal.right.y) {
           prevVal.right.y = currVal.y;
        }
    }
    return prevVal;
}
var r = points.reduceRight(findRectanglePoints);	
console.log(r);

At the end of this article we want you to show how to check if object is an array and list all of the array properties.

Array.isArray(obj) - static method of Array class, it returns true if object is an array and false otherwise. Method isArray has some advantages over instanceof operator: it’s faster and correctly check type of arrays from different iframes.

We have some list in html content. We want to get all of its items and put them into array to make some modification. Method getElementsByTagName returns HTMLCollection array-like object which is not very useful. To use all array methods we can transform HTMLCollection to array and in the following example we do that using slice method. Before and after we test variable if it is an array using isArray method.

function traceElement(el,i,a) {
    console.log( i + ' > ' + el );
}    
var arrayLike = document.getElementsByTagName('li');
console.log(Array.isArray(arrayLike)); // false
console.log(arrayLike instanceof Array) // false
var simpleArray = Array.prototype.slice.call(arrayLike);
console.log(Array.isArray(simpleArray)); // true
console.log(simpleArray instanceof Array) // true
simpleArray.forEach(traceElement);

There are a few more new methods brought by ECMAScript 6 but unfortunately they are not implemented till now so we skipped them. You can list all array methods supported by your browser enginge by executing the following snippet:

console.log(Object.getOwnPropertyNames(Array.prototype))

Summary

ECMAScript 5’s array methods allows to work with arrays in an easy and functional way. They can turn out very useful and efficient part of every developer’s toolbox.