Javascript: arrays

·

11 min read

Like objects arrays are one of the fundamental data structures in any programming language. In statically typed languages such as C++, arrays are ordered collections of elements of the same type. Javascript being an untyped programming language, arrays can contain different types like strings, numbers, booleans, and nested arrays. Javascript arrays integer based ,dynamic in which they grow and shrink as needed. Arrays are a special kind of object where the properties happened to be integers. But the built-in implementation of arrays makes arrays faster than the objects in accessing the elements.

Creating Arrays

arrays can be created in the following ways

  1. Arrays literals

  2. new Array()

  3. Arrray.from()

  4. Array.of()

  5. Array literals

    The simplest way to create an array is to include a literal. It has the below syntax.

     let array = [1,2,3,4,]
     let items = ["apples",true,4,"oranges"]
     let nestedArray = [2,[3,4],6,[5,6]]
    
  6. Array() constructor

    A new keyword followed by Array() invocation will create an array. It accepts different no of arguments. Based on the arguments it can be created in different ways.

     //creates an empty array with zero arguments  
     let array = new Array()
    
     //creates an array with a fixed size if one numeric argument is passed.  
     let array = new Array(10)
    
     //creates an array with elements if a non numeric or two or more numeric arguments are //passed.
     let array = new Array(5,6,3,2)
    
  7. Array.of()

    The previous Array() constructor creates an array of fixed length if you provide only one argument. This way it cannot create an array of single elements. This is where Array.of() solves. It was introduced in ES6. It creates an array of elements irrespective of the no of arguments that are passed.

     let a = Array.of(1)    // [1]
     let b = Array.of(3,4,5,5) // [3,4,5,5]
     let c = Array.of() // [] empty array
    
  8. Array.from()

    It is another method introduced in ES6. It expects an iterable or array-like object as its first argument and returns a new array containing elements of the arguments array. one of the uses of this method is it returns a true array. So , array-like objects can be converted into true arrays where we can use all the array methods which are not possible for array-like objects. Objects which have integer indexes and a length property are known as array-like objects. They do not have methods like the true arrays have.

     let one = [3,4,4,5]
     let two = Array.from(one)  //  [3,4,5]
    

Querying and setting array elements

just like objects you can use square brackets to access an element as well as to set the element. As we know array indexes are integers of 32 bits. Arrays are also a type of object. It can behave like an object. One key difference between an object and an array is that the array has a length property. Even arrays convert the indexes into a string and store it just like objects store properties. But only properties that are non-negative integers are treated as indexes and also the length is updated for indexes only not for properties.

If you provide a property that cannot be converted to an integer, it converts that to a string and saves it as a normal property. If you provide a value that can be converted integer, it converts that to a string, stores it as an index and the length is also updated.

let x = [3, 4]; // length is 2
x[2] = 5; // length is 3
x[-3] = 7; // length is 3 with "-3" as normal property
x[4.000] = 10; // length is 4 with "4" as index same as x[4]
x[1.23] = 0; // length is 3 with "1.23" as normal property

Deleting properties

just like objects you can delete array elements using the delete operator. It expects one operand of an object property or an array index. It deletes the property and not just the value. To check its presence use the "in" operator which returns false if the property or element is not present. The point to note here is the length of the array does not change when you delete it and if you try to access it, it returns undefined.

let x = [1,2] // length is 2
delete x[0] // length is 2
0 in x         // false
x[0]           // undefined

Iterating Arrays

Arrays can be iterated in the following ways

  1. for(of) loop

  2. for(in) loop

  3. forEach() method

Array methods

These methods defined in the Array class are the most powerful and are used a lot in many situations. Some of these methods return a new array and some of them change the given array itself. The following figure shows some of the array methods. The below array methods can be largely categorized into the following parts.

  • iterator methods loop for the elements of the array typically invoking a function you specify on each element.

  • stack and queue type of methods add, and remove elements from the beginning and end of the array.

  • subarray type of methods extract, insert, delete, fill, and copy contiguous regions of the array.

  • searching and sorting type of methods are for searching for a specific element and sorting the elements in the way we want.

Untitled.png

  1. forEach( callback, thisArg )

    It is an iterator method. It loops over the elements of the given array. It accepts two arguments. The first argument should be a call-back function and it is invoked for each element of the array. The callback function accepts three arguments, the value of the element, index, and array respectively. If you provide lesser than mentioned the rest of the arguments are ignored. The second argument is known as thisArg, it will become the value of this inside the callback function. so the function you provide as the first argument can be invoked as a method of the second argument using this.

     // compute the sum of elements of an array
     let data = [2,3,4,5,6,7]   // 
     let sum = 0
     data.forEach(value => {sum+=value})
     console.log(sum) // 27
    
     // increment each value of the array
     data.forEach((v,i,a) => {a[i]= v+1})
     console.log(data)  // [3,4,5,6,7,8]
    
  2. map( callback, thisArg )

    like forEach, the map also iterates over the elements of the given array. The arguments are the same as forEach. It does not change the original array and returns a new array containing the elements returned from the callback function. For sparse arrays, it does not invoke the callback function but the returned array will be sparse as the original array.

     let data = [2,3,4,5,6]
     let x = data.map(x =>{x*x})
     x // [4,9,16,25,36]
    
  3. filter( callback, thisArg )

    filter method returns a new array containing a subset of elements of the original array. The arguments are the same as forEach(). The callback function should return a true or false which determines the presence of the element in the new array.

     let a = [5, 4, 3, 2, 1];
     a.filter(x => x < 3) // => [2, 1]; values less than 3
     a.filter((x,i) => i%2 === 0) // => [5, 3, 1]; every other
    
  4. find( callback, thisArg ) and findIndex( callback, thisArg )

    These two iterate over the array of the elements looking for the element where your callback function returns true. It stops as soon as it gets true. The arguments are the same as the forEach. find() returns the matching element and findIndex() returns the index of that matching element. If they don't find any matching element find() returns undefined and find index() returns -1;

     let a = [1,2,3,4,5];
     a.findIndex(x => x === 3) // => 2; the value 3 appears at index 2
     a.findIndex(x => x < 0) // => -1; no negative numbers in the array
     a.find(x => x % 5 === 0) // => 5: this is a multiple of 5
     a.find(x => x % 7 === 0) // => undefined: no multiples of 7
    
  5. Every( callback, thisArg ) and some( )

    These are pretty straightforward. These two methods return either true or false values. Every() returns true if the callback function returns true for every array element otherwise false. some() returns false if the callback function returns false for every array element otherwise true.

     let a = [1,2,3,4,5];
     a.every(x => x < 10) // => true: all values are < 10.
     a.every(x => x % 2 === 0) // => false: not all values are even
     a.some(x => x%2===0) // => true; a has some even numbers.
     a.some(x=>x>10) // => false; a has no element greater than 10
    
  6. reduce ( callback, initial value ) and reduceRight ( callback, initial value )

    These two methods combine the elements of the array based on the condition inside the callback function and reduce it to a single value. These two take two arguments a callback function and an initial value. The call back function accepts four arguments accumulator,element,index,array respectively. At the begining the value of accumulator is the initial value you passed as the second argument. If you do not pass an initial value the accumulator value would be first element of the array and second argument of the callback function will be the second element of the array. The subsequent calls the value of the accumulator is the return value of the callback function. recuceRight() is same as reduce() but it does it starting from the end of the array.

     let a = [1,2,3,4,5];
     a.reduce((x,y) => x+y, 0) // => 15; the sum of the elements
     a.reduce((x,y) => x+y) //=> 15 no initial value, accumlator will be first element of the array
     a.reduce((x,y) => x*y, 1) // => 120; the product of the elements
     a.reduce((x,y) => (x > y) ? x : y) // => 5; the largest of the values
    
     // Compute 3^(2^2). Exponentiation has right-to-left precedence
     let a = [3, 2, 2];
     a.reduceRight((acc,val) => Math.pow(val,acc)) // => 81
    
  7. flat( depth ) and flatMap( callback, thisArg)

These two methods return a new array containing same elements but flattens any nested arrays. These two accept one argument which specifies the no of levels it needs to flatten the array. If no argument is passed it flattens to one level. flatMap() is nothing but it flattens the array returned by the map(). Its same as array.map().flat() but more efficient.

[1, [2, 3]].flat() // => [1, 2, 3]
[1, [2, [3]]].flat() // => [1, 2, [3]]

let a = [1, [2, [3, [4]]]];
a.flat(1) // => [1, 2, [3, [4]]]
a.flat(2) // => [1, 2, 3, [4]]
a.flat(3) // => [1, 2, 3, 4]
a.flat(4) // => [1, 2, 3, 4]

let phrases = ["hello world", "the definitive guide"];
let words = phrases.flatMap(phrase => phrase.split(" "));
words // => ["hello", "world", "the", "definitive", "guide"];
  1. concat( value1,value2,value3,..... )

It returns a new array containing the elements of the original array and the arguments which are passed. If you pass arrays as arguments , the elements are copied not the arrays. But if you pass nested arrays they are not flattened and are shallow copied as they are.

let a = [1,2,3];
a.concat(4, 5) // => [1,2,3,4,5]
a.concat([4,5],[6,7]) // => [1,2,3,4,5,6,7] arrays are flattened one level
a.concat(4, [5,[6,7]]) // => [1,2,3,4,5,[6,7]] but not nested arrays
a // => [1,2,3]; the original array is unmodified

let x1 = [[4,5],6]
let output = a.concat(x1) // => [1,2,3,[4,5],6] shallow copy
x1[0].push(9) // => [[4,5,9],6]
console.log(output) // => [1,2,3,[4,5,9],6] changes are reflected
  1. push() and pop()

push() inserts one or more elements at the end of the array and returns the new length. It does not flatten if the arguments are arrays. pop() deletes the last element of the array and returns the deleted element.These both modify the original array.

let stack = []; //  []
stack.push(1,2); // [1,2];
stack.pop(); // stack == [1]; returns 2
stack.push(3); // stack == [1,3]
stack.pop(); // stack == [1]; returns 3
stack.push([4,5]); // stack == [1,[4,5]]
stack.pop() // stack == [1]; returns [4,5]
stack.pop(); // stack == []; returns 1
  1. shift() and unshift()

unshift inserts one or more elements at the begining and shifts all other elements to higher indexes and returns the new length. shift() removes an element from the begining of the array and shifts all other elements to lower indexes.

let a = [];
a.unshift(1) // a == [1]
a.unshift(2) // a == [2, 1]
a.shift() // a== [1]
  1. slice()

slice() returns a new array which is subarray or whole array depending on the arguments. It accepts two arguments start,end. slice() includes elements from the start index to end-1 index.If you specify only one argument, it includes all the elements from that index. It also accepts negative arguments which specify the index from the back of the array. Like -1 specifies first element from the back.

let a = [1,2,3,4,5];
a.slice(0,3); // Returns [1,2,3]
a.slice(3); // Returns [4,5]
a.slice(1,-1); // Returns [2,3,4]
a.slice(-3,-2); // Returns [3]
  1. splice()

splice() can insert, remove elements. It modifies the original array. The first two arguments are start index, length. Length is no of elements to be deleted after the start index. Any arguments followed by these two will be inserted where the elements are deleted.

let a = [1,2,3,4,5,6,7,8];
a.splice(4) // => [5,6,7,8]; a is now [1,2,3,4]
a.splice(1,2) // => [2,3]; a is now [1,4]
a.splice(1,1) // => [4]; a is now [1]
let a = [1,2,3,4,5];
a.splice(2,0,"a","b") // => []; a is now [1,2,"a","b",3,4,5]
a.splice(2,2,[1,2],3) // => ["a","b"]; a is now [1,2,
[1,2],3,3,4,5]

Source:

  1. JavaScript: The Definitive Guide, 7th Edition by David Flanagan