Simplify Your JavaScript with Arrays

ยท

33 min read

What is an Array?

Arrays are used to store multiple values in JavaScript. You can think of an array as an ordered list of values. Arrays organize their items in logical order or sequentially. Also, arrays have built-in methods that make it easy to lookup, add, or remove elements based on their position.

Arrays are organized in a logical or sequential manner. This is very helpful when accessing items because the computer already knows where the value is located. It's because there's a number that refers to the location or position where the value is stored. We call this index.

The array index starts with 0, and as you move further to the right, the index increases by one. This numbering method is called Zero-Based Numbering.

JavaScript arrays are dynamic, which means the size of the array can grow and shrink as needed. It does not have static arrays where you have to set a fixed length specifically.

You can store any type of data in an array. For example, you can store boolean, strings, numbers, characters, objects, or even other arrays. And, it's possible to create an array with elements of multiple data types. This means creating an array with a number in the first position, a string in the second, an object in the third, etc.

Properties of Array:

length:

The length property returns the size of the array. When you access the length, it simply returns the value of the biggest index + 1.

If you wonder why it is adding 1 to the index, if you remember, the index starts with 0, so to get the actual number of elements, it doesn't count the elements; it gets the biggest numeric index plus one.

You can find out the length of the array with the length property as below:

let names = ["A", "B", "C", "D"];
console.log(names.length); // 4

How to Check if the Given Value is an Array:

To check whether the given value is an array or not we use Array.isArray(value) method. This method returns true if the given value is an array.

Array.isArray('simran');   // false
Array.isArray(['simran', 146, 52]);  // true
Array.isArray(undefined);  // false
Array.isArray({totalMarks: 93}); // false

Ways of Creating an Array:

There are multiple ways through which we can create an array.

Creating an array via array-literal

The array literal uses the square brackets [] to wrap a list of elements separated by commas. This is the most common way of creating an array.

const array = [1, 2, 3];

Creating an array using "new" keyword:

We can create an array using new and then the Array constructor which wraps all the list of items that are to be placed in the array as shown below:

const array = new Array(1, 2, 3, 4, 5);

If there is more than 1 element that is to be placed in the array, the above approach would push the elements to the array.

In case, we pass a single value in the Array constructor, it would be treated as the overall size of the array.

const array = new Array(1, 2); // Returns [1, 2]
const myArray = new Array(5); // Returns (empty * 5)
Above, array of 5 slots would be created which are as of now empty
// Array with one string
let array = new Array("ABC");
console.log(array.length); // 1
console.log(arr[0]);    // "ABC"

// Array with one numerical value
let myArray = new Array(4);

console.log(myArray.length);  // 4

console.log(myArray[0]);  // undefined
console.log(myArray[1]);  // undefined
console.log(myArray[2]);  // undefined
console.log(myArray[3]);  // undefined

In the above example, the array myArray length is set to 4 since we have added one numerical value that sets the length. And it has created a list with no elements, so the value of each position is undefined.

To create an empty array, in this approach we can use

const myArray = new Array(); // Empty Array

Creating an Array with Array constructor by Omitting a new keyword

You can omit the "new" keyword and mention only the Array Constructor and it would behave the exact way.

const array = Array(1, 5); // Returns [1, 5];
const myArray = Array(5); //Returns (empty * 5);

Creating an Array using Array.of() method:

Array.of() creates an array with specified elements, unlike the Array constructor. If you define one element, it takes the one element into the array without treating it as the length of the array.

const yetMoreNumbers = Array.of(1, 2); // [1, 2]
const moreNumbers = Array.of(5); // [5]
const arrayNumbers = Array(5); // (empty * 5)
console.log(Array.of(5).length);    //1
console.log(Array(5).length);        //5

So, this proves that the Array.of() method creates an array of 1 element while the Array constructor creates an array of 5 elements.

Creating Array using Array.from()

Array.from() unlike Array.of() doesn't take multiple numbers as input. Instead, it takes an iterable or array-like object and splits it into an array.

const array1 = Array.from(1, 2, 3) //Throws error
//Instead, it takes an iterable- an array like object
const array2 = Array.from('Hi'); // Returns ["H","i"];

Array.from() is useful for traversing DOM and quering the NodeList types of data structures.

const listItems = document.querySelectorAll('li');
const arrayListItems = Array.from(listItems);

Array Methods:

Searching Methods:

at()

at() accepts an integer and returns the element that is present at that index in an array.

If a positive Integer is passed, the search starts from left and the element is returned. If a negative integer is passed, the search starts from the right and the element found at that index is returned. An integer that is out of range of the array's length is returned as undefined.

Array.prototype.at = function (index) {
  if (index >= 0) {
    return this[index];
  } else {
    return this[this.length + index];
  }
};
// You can write your own custom implementations of every method using Array.prototype
const array = [1, 2, 3, 4, 5];
// Positive Integer is passed
console.log(arr.at(3)); // Returns 4
// Negative Integer is passed
console.log(arr.at(-1)); // Returns 5
// Integer which is out of range
console.log(arr.at(7)); // Returns undefined

indexOf()

indexOf() method accepts an element and returns the first position at which it found the element and then stops the search. This implies, if you have duplicate elements in an array, the index of the first found element will be returned. indexOf() doesn't work with reference types like Objects. It returns -1. It is built to search only the primitive types.

Array.prototype.indexOf = function (element, fromIndex = 0) {
  for (let i = fromIndex; i < this.length; i++) {
    if (this[i] === element) {
      return i;
    }
  }
  return -1;
};
const arr = [1, 2, 3, 4, 5, 1];
// Test case 1:
console.log(arr.indexOf(1)); // output: 0 
// Test case 2:
console.log(arr.indexOf(7)); // output: -1

If Element is found, returns the index of the element.

If Element is not found, it returns -1

const personData = [{"name":"ABC"},{"name":"XYZ"}]
// now to find out where XYZ is
personData.indexOf({"name":"XYZ"}) // Returns -1

Because indexOf() doesn't work with reference values. We have separate methods to search for the reference types.

lastIndexOf()

lastIndexOf() the method accepts an element and returns the last index at which the element is present in the array.

If Element is found, returns the index of the last element where the given element is present.

If Element not found, returns -1

Array.prototype.lastIndexOf = function (
  element,
  fromIndex = this.length - 1
) {
  for (let i = fromIndex; i >= 0; i--) {
    if (this[i] === element) {
      return i;
    }
  }
  return -1;
};
const arr = ["a", "b", "c", "d", "e", "a"];
// Test case 1:
console.log(arr.lastIndexOf("a")); //  output: 5 
// Test case 2:
console.log(arr.lastIndexOf("z")); //  output: -1

lastIndexOf() doesn't work with reference types like indexOf(). They are designed to search only primitive type elements. There are find() and findIndex() as alternate methods to search the reference types.

The reason for this behavior is because of the reference value. Objects are reference values and therefore in the end, We are creating a brand new object and passing it to indexOf(). Behind-the-scenes indexOf() compares all the values. Hence, even if two objects look similar they are never equal.

find():

find() method accepts a function that executes on every element of the array. You can either create an anonymous function or write a function explicitly and pass the reference to the method. This function can accept up to 3 arguments.

Now, who is calling that function which you pass inside find(). JavaScript does it for you. find() doesn't create a copy of an array.

The first argument will always be that single object of an array. The second argument will always be the index of that single element. The third argument is the complete array.

The first element in the given array which satisfies the condition will be returned. If the element is found, that element is returned. If not found, undefined will be returned.

// Custom Implementation
Array.prototype.find = function (cb) {
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i], i, this)) {
      return this[i];
    }
  }
};
// With Primitive types:
const arr = [10, 20, 31, 44, 55, 67];
const result1 = arr.find((element) => element % 20 === 0);
console.log(result1);
// output: 20
const result2 = arr.myFind((element) => element % 3 === 0);
console.log(result2);
// output: undefined
// With reference types
const personData = [{name: 'ABC'},{name:'DEF'}];
const person = personData.find((person, index, personsArray) => {
    return person.name === 'XYZ';
})
// Output: undefined
const maxIndex = personData.findIndex((person, index, personsArray) => {
    return person.name === 'ABC'
});
// Output: 0

findIndex()

findIndex() accepts a function as input that executes on every element in an array. The index of the first element that fulfills the testing function is returned.

const arr = [1, 2, 3, 4, 5];
const result = arr.findIndex((item) => item > 3);
console.log(result); //1
const inventory = [
{name: "apples", quantity: 2},
{name: "oranges", quantity: 2},
{name: "Cherries", quantity: 1}
];
const result = inventory.findIndex(({name}) => name === "Cherries");
console.log(result); // 2;

Difference between find() and findIndex()

  1. Both find() and findIndex() do not modify the original array.

  2. find() will return the value of the first element based on the provided condition and returns undefined if none of the elements passed the condition.

  3. findIndex() will return the index of the first element based on the condition and returns -1 if none of the elements passed the condition.

findLast()

The findLast() method iterates the array in reverse order and returns the value of the first element that satisfies the provided testing function. If no elements satisfy the testing function, undefined is returned.

Array.prototype.findLast = function (cb) {
  for (let i = this.length; i >= 0; i--) {
    if (cb(this[i], i, this)) {
      return this[i];
    }
  }
};
const array1 = [5, 12, 50, 130, 44];
const found = array1.findLast((element) => element > 45);
console.log(found); // Expected output: 130

findLastIndex()

The findLastIndex() method iterates the array in reverse order and returns the index of the first element that satisfies the provided testing function. If no elements satisfy the testing function, -1 is returned.

const array1 = [5, 12, 50, 130, 44];
const isLargeNumber = (element) => element > 45;
console.log(array1.findLastIndex(isLargeNumber));
// Expected output: 3
// Index of element with value: 130
Array.prototype.findLastIndex = function (cb) {
  for (let i = this.length; i >= 0; i--) {
    if (cb(this[i], i, this)) {
      return i;
    }
  }
  return -1;
};

indexOf()

includes() is an easy way to find out if a value is present in an array if you don't want to go through the index process. If the element is present it returns true else returns false.

Array.prototype.includes = function (element, fromIndex = 0) {
  for (let i = fromIndex; i < this.length; i++) {
    if (this[i] === element) {
      return true;
    }
  }
  return false;
};
const arr = ["a", "b", "c", "d", "e", "a"];
// Test case 1:
const result1 = arr.myIncludes("a");
console.log(result1) // output: true

// Test case 2:
const result2 = arr.myIncludes("z");
console.log(result2) // output: false

some()

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array.

Atleast one element satisfies the condition returns true else false.

Array.prototype.some = function (cb) {
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i])) {
      return true;
    }
  }
  return false;
};
// Test case 1:
const arr1 = [10, 21, 32, 43, 54, 65];
const hasEven = arr1.some((el) => el % 2 === 0);
console.log(hasEven);
// output: true 

// Test case 2:
const arr2 = [10, 20, 30, 40, 50, 60];
const hasOdd = arr2.mySome((el) => el % 2 !== 0);
console.log(hasOdd);
// output: false

every()

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. every() method accepts a callback function that executes on every element of the array.

All elements satisfy the condition - true

Some elements satisfy the condition - false

No element satisfies the condition - false

Array.prototype.every = function (cb) {
  for (let i = 0; i < this.length; i++) {
    if (!cb(this[i])) {
      return false;
    }
  }
  return true;
};
// Test case 1
const arr1 = [10, 20, 30, 40, 50, 60];
const isAllEven = arr1.every((el) => el % 2 === 0)
console.log(isAllEven); // output: true 

// Test case 2
const arr2 = [10, 21, 30, 41, 50, 60];
const isAllEven1 = arr2.every((el) => el % 2 === 0)
console.log(isAllEven1); // output: false

// Test case 3
const arr3 = [11, 21, 30, 41, 51, 61];
const isAllEven2 = arr3.every((el) => el % 2 === 0)
console.log(isAllEven2); // output: false

Iterative Methods:

forEach()

forEach() accepts a callback function and executes it on every element. It returns undefined. The callback function is not invoked on uninitialized elements.

Array with all elements- forEach() executes all elements

Sparse array - forEach() doesn't execute the uninitialized array elements.

Array.prototype.forEach = function (cb) {
  for (let i = 0; i < this.length; i++) {
    if (typeof this[i] !== "undefined") {
      cb(this[i], i, this);
    }
  }
};
// Test case 1:
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.forEach((element, index) => console.log({[index]: element}));
// output:
// {0: 'a'}
// {1: 'b'}
// {2: 'c'}

// Test case 2:
const arr1 = ["a", , "c"];
arr1.myForEach((element, index) => console.log({[index]: element}));
// output:
// {0: 'a'}
// {2: 'c'}

Map, Filter and Reduce

map()

Sometimes we have an array of objects that we wish to modify/add properties of each object, other times we might have an array of strings that we which to turn all of them lowercase. In reality, there might be countless situations in which map is your savior and itโ€™s really simple to use.

Array.prototype.map = function (cb) {
  let result = [];
  for (let i = 0; i < this.length; i++) {
    const value = cb(this[i], i, this);
    result.push(value);
  }
  return result;
};
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const newArray = array.map((value, index, array) => {
    return value * 2;
});
console.log(array);
console.log(newArray);
const songs = [
{ id: 1, name: "Curl of the Burl", artist:"Mastodon"},
{ id: 2, name: "Oblivion", artist: "Mastodon"},
{ id: 3, name: "Flying Whales", artist: "Gojira"},
{ id: 4, name: "L'Enfant Sauvage", artist: "Gojira"}
];
let songNames = songs.map(song => {
    return song.name;
});
console.log(songNames);

filter()

filter() receives same arguments as map(). The only difference is that the callback needs to return either true or false. If it returns true then the array keeps that element and if it returns false the element is filtered out.

Array.prototype.filter = function (cb) {
  let result = [];
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};
let strings = ["hello","matt","Mastodon","Cat","Dog"];
let filteredStrings = strings.filter(str => { str.includes("at"); })
console.log(filteredStrings); // ["matt","Cat"]

reduce()

It is similar to both map and filter but, it differs in the callback arguments. The callback now receives the accumulator (it accumulates all the return values. Its value is the accumulation of the previously returned accumulations), the current value, the current index and finally the whole array.

Array.prototype.reduce = function (cb, initialValue) {
  let result;
  let startIndex = 0;
  if (arguments.length <= 1) {
    result = this[0];
    startIndex = 1;
  }
  if (arguments.length >= 2) {
    result = initialValue;
  }
  for (let i = startIndex; i < this.length; i++) {
    result = cb(result, this[i]);
  }
  return result;
};
const array = [1, 2, 3, 4, 5];
const sum = array.reduce((accum, currentValue, currentIndex, array)=> {
    return accum + currentValue;
}, 0);
const average = sum / (array.length);
console.log(average);
const songs = [
{ id: 1, name: "Curl of the Burl", artist:"Mastodon"},
{ id: 2, name: "Oblivion", artist: "Mastodon"},
{ id: 3, name: "Flying Whales", artist: "Gojira"},
{ id: 4, name: "L'Enfant Sauvage", artist: "Gojira"}
];
let object = songs.reduce((accum, currentValue) => {
    let artist = currentValue.artist;
    const artistCount = accum[artist] ? accum[artist]+1 : 1;
    return {
        ...accum, 
        [artist]: artistCount
    };
}, {});
console.log(object); // {Mastodon: 2, Gojira: 2};

Chaining map(), filter() and reduce():

Map, Filter and Reduce are chainable and, together, you hold an unlimited amount of mystical power! ๐Ÿ˜„ โœจ โœจ

Given these more complex arrays of songs where we have an array of arrays with songs from Spotify and LastFM. Each song has a duration in seconds.

Now, we must get a string separated by commas with all the songs that have a duration superior to 3 minutes.

const spotifySongs = [
{id: 1, name: "Curl of the Burl", artist:"Mastodon", duration: 204},
{id: 2, name: "Oblivion", artist: "Mastodon", duration: 306},
{id: 3, name: "Flying Whales", artist: "Gojira", duration: 444},
{id: 4, name: "L'Enfant Sauvage", artist: "Gojira", duration: 246}
];
const lastFMSongs = [
{id: 5, name: "Chop Seuy", artist:"System of Down", duration: 192},
{id: 6, name: "Throne", artist:"Bring me the Horizon", duration: 186},
{id: 7, name: "Destrier", artist:"Agent Fresco", duration: 132},
{id: 8, name: "Out of the Black", artist:"Royal Blood", duration: 240},
];
const allSongs = [spotifySongs, lastFMSongs];
const songNames = allSongs.reduce((acc, currentValue) => {
    return acc.concat(currentValue);
}, []).map(song => {
    return {...song, duration: Math.floor(song.duration/60)};
}).filter(song => song.duration > 3).map(song => song.name)
.join(" , ");
console.log(songNames);

Why use map(), filter() and reduce()?

  • You work directly with the current value instead of accessing it through an index (i.e array[i]);

  • Avoid mutation of the original array, therefore, minimizing side effects;

  • No need to manage a for loop;

  • No more creating empty arrays and push stuff into them;

  • REMEMBER THE RETURN STATEMENT IN THE CALLBACK;

reduceRight()

The reduceRight() method applies a function against an accumulator and each value of the array (from right to left) to reduce it to a single value.

Array.prototype.reduceRight = function (cb, initialValue) {
  let result;
  let startIndex = this.length - 1;
  if (arguments.length <= 1) {
    result = this[this.length - 1];
    startIndex = this.length - 2;
  }
  if (arguments.length >= 2) {
    result = initialValue;
  }
  for (let i = startIndex; i >= 0; i--) {
    result = cb(result, this[i]);
  }
  return result;
};
const array1 = [[0, 1], [2, 3], [4, 5]];
const result = array1.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue));
console.log(result);
// Expected output: Array [4, 5, 2, 3, 0, 1]

Manipulating Methods

unshift()

unshift() adds elements at the start of an array. It mutates the original array. It returns the new length of the array.

Array.prototype.unshift = function () {
  if (arguments.length > 0) {
    // move elements of the array ahead
    for (let i = this.length - 1; i >= 0; i--) {
      this[i + arguments.length] = this[i];
    }
    // add the args elements at the start
    for (let i = 0; i < arguments.length; i++) {
      this[i] = arguments[i];
    }
  }
  return this.length;
};
const array = [1, 2, 3, 4, 5];
const result = array.unshift(6, 7, 8, 9);
console.log(result); // 9
console.log(array); // [6, 7, 8, 9, 1, 2, 3, 4, 5];

pop()

pop() removes the last element of an array. It mutates the original array. It returns the removed element. It returns undefined if it is an empty array. It returns the length of the array if it is a non-empty array.

Array.prototype.pop = function () {
  if (this.length > 0) {
    let lastEl = this[this.length - 1];
    this.length -= 1;
    return lastEl;
  }
};
// Test case 1:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = array.pop();
console.log(result); // 8
console.log(array); // [1, 2, 3, 4, 5, 6, 7, 8];

// Test case 2:
const emptyArray = [];
const result1 = emptyArray.myPop();
console.log(result1); // undefined
console.log(emptyArray); // [];

push()

push() adds an element at the end of an array. It mutates the original array and returns the new length of an array.

Array.prototype.push = function () {
  for (let i = 0; i < arguments.length; i++) {
    this[this.length] = arguments[i];
  }
  return this.length;
};
const array = [1, 2, 3, 4, 5];
const result = array.push(6, 7, 8, 9);
console.log(result) // 9
console.log(array) // [1, 2, 3, 4, 5, 6, 7, 8, 9];

shift()

shift() removes the first element of an array. It mutates the original array. It returns the removed element. It returns undefined if the array is empty. It returns new length of an array of array is not empty.

Array.prototype.shift = function () {
  if (this.length > 0) {
    const element = this[0];
    for (let i = 0; i < this.length; i++) {
      this[i] = this[i + 1];
    }
    this.length -= 1;
    return element;
  }
};
// Test case 1:
const array = [1, 2, 3, 4, 5];
const result = array.shift();
console.log(result); // 1
console.log(array); // [2, 3, 4, 5];

// Test case 2:
const emptyArray = [];
const result1 = emptyArray.shift();
console.log(result1); // undefined
console.log(emptyArray); // [];

fill()

fill() fills or replaces elements in an array with a given value. It accepts three arguments: value, start and end. It fills the elements between start and end with the given value.

  • No start and end provided- Fills all the elements with the given value.

  • start provided end not provided: Fills all the elements from start with the given value to the end of the array.

  • start and end provided: fills all the elements between start and end with the value given. (element at the end index is not included)

  • Negative Indexes: counts the index backward from the last element of the array

Array.prototype.fill = function (value, start = 0, end = this.length) {
  if (start < 0) {
    start = this.length + start;
  }
  if (end < 0) {
    end = this.length + end;
  }
  for (let i = start; i < end; i++) {
    this[i] = value;
  }
  return this;
};
// Test case 1:
const arr1 =[1, 2, 3, 4, 5];
const result1 = arr1.fill("*");
console.log(result1) // ["*", "*", "*", "*", "*"];

// Test case 2:
const arr2 = [1, 2, 3, 4, 5];
const result2 = arr2.fill("*", 1);
console.log(result2) // [1, "*", "*", "*", "*"];

// Test case 3:
const arr3 = [1, 2, 3, 4, 5];
const result3 = arr3.fill("*", 1, 3);
console.log(result3) // [1, "*", "*", 4, 5];

// Test case 4:
const arr4 = [1, 2, 3, 4, 5];
const result4 = arr4.fill("*", -4, -1);
console.log(result4) // [1, "*", "*", "*", 5];/

reverse()

reverse() method reverses the array. It mutates the original array and returns the reversed array.

Array.prototype.reverse = function () {
  let start = 0,
    end = this.length - 1;
  while (start < end) {
    let temp = this[start];
    this[start] = this[end];
    this[end] = temp;
    start++;
    end--;
  }
  return this;
};
const arr = [1, 2, 3, 4, 5];
const result = arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1]
console.log(result); // [5, 4, 3, 2, 1]

sort()

sort() method sorts an array. By default, sort() converts all the elements of an array to string and sorts them in ascending order. We can provide a comparator function to sort the elements in a custom order.

  • If the comparator function is not provided - sort() converts all the elements to strings and then sorts them in ascending order.

  • If the comparator function is provided - It sorts all the elements based on the function.

function defaultComparator(a, b) {
  a = a.toString();
  b = b.toString();
  if (a < b) {
    return -1;
  }
  if (a > b) {
    return 1;
  }
  return 0;
}

Array.prototype.sort = function (cb = defaultComparator) {
  for (var i = 0; i < this.length; i++) {
    for (var j = i + 1; j < this.length; j++) {
      if (cb(this[i], this[j]) > 0) {
        var swap = this[i];
        this[i] = this[j];
        this[j] = swap;
      }
    }
  }
};
const prices = [10.99, 5.99, 3.99, 6.99]
const sortedPrices = prices.sort((a, b) => {
    if(a > b) {
        return 1;
    } else if(a === b) {
        return 0;
    } else {
        return -1;
    }
});
console.log(sortedPrices); // returns in ascending order
console.log(sortedPrices.reverse()); // returns in descending order

copyWithin()

copyWithin() copies a portion of the array to another location within the same array. It accepts three arguments: target, start, and end.

  • If target is provided and start, end are not provided - It will copy the element from first element to the target index.

  • target and start are provided but end is not provided - It will copy the element present at the start index to the target index.

  • target, start and end all are provided- It will copy the elements lying between the start and end to the target index. (element present at end index will not be included).

Array.prototype.copyWithin = function (target = 0,start = 0,end = this.length) {
  if (target < 0) {
    target = this.length + target;
  }
   if (start < 0) {
    start = this.length + start;
  }
  if (end < 0) {
    end = this.length + end;
  }
  for (let i = start; i < end && target < this.length; i++) {
    this[target] = this[i];
    target++;
  }
  return this;
};
// Test Case 1:
const array1 = [1, 2, 3, 4, 5]
array1.copyWithin(4); 
console.log(array1) // output: [1, 2, 3, 4, 1]

const array2 = [1, 2, 3, 4, 5]
array2.copyWithin(3, 1); 
console.log(array2) // output: [1, 2, 3, 2, 3]

const array3 = [1, 2, 3, 4, 5, 6]
array3.copyWithin(0, 3, 5); 
console.log(array3) // output: [4, 5, 3, 4, 5, 6]

Other Methods:

slice()

The slice() method copies a chunk (or slice) from an array and returns that copied part as a new array. It does not modify or change the original array. Instead, it creates a new shallow copy of the original array.

This method takes two optional arguments. The first argument is the startIndex, and the second is the endIndex. If you do not pass any arguments, the entire original array will be copied by default. Also, If the arguments you pass are greater than the actual array, it will return an empty array.

// No arguments
array.slice();
 // One argument
array.slice(startIndex);
 // Two arguments
array.slice(startIndex, endIndex);

Parameters

  • startIndex:

    • The index where the slice should begin.

    • If the value is omitted, it will start at 0.

  • endIndex:

    • The slice will end before this index. So, for example, adding index 4 will slice up to index 3, omitting the value of index 4.

    • If the endIndex value is omitted, it will slice to the end of the array.

Custom Implementation:

Array.prototype.slice = function (start = 0, end = this.length) {
  if (start < 0) {
    start = this.length + start;
  }
  if (end < 0) {
    end = this.length + end;
  }
  let arr = [];
  for (let i = start; i < end; i++) {
    arr.push(this[i]);
  }
  return arr;
};
const arr = 
["a", "b", "c", "d", "e"];

// Test case 1:
const result1 = arr.slice(2);
console.log(result1);
// output: ["c", "d", "e"]

// Test case 2:
const result2 = arr.slice(2, 4);
console.log(result2);
// output: ["c", "d"]

// Test case 3:
const result3 = arr.slice(-1);
console.log(result3);
// output: ["e"]

// Test case 4:
const result4 = arr.slice(-5, -2);
console.log(result4);
// output: ["a", "b", "c"]

Let's slice an array with no arguments:

let favoriteFood = ["๐Ÿ•", "๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"];
let slicedArray = favoriteFood.slice();
console.log(slicedArray); // ["๐Ÿ•", "๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"]
console.log(favoriteFood === slicedArray);  //false

In the above example, since there are no arguments, it has returned a copy of the entire array.

One important thing to note here is that these two arrays are not equal! They are two separate arrays containing the same values inside them. So if you check their equality as in the example, it will return false.

Now let's check how we can slice an array with a single argument.

let favoriteFood = ["๐Ÿ•", "๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"];
let slicedArray = favoriteFood.slice(1);
console.log(slicedArray); // ["๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"]

When you pass a single argument to the slice() method, it grabs all the elements from that argument until the end of the array, including the index in the argument. So in our example, we have made a copy from index 1 to favoriteFood.length - 1.

Let's move on to slicing an array by passing two arguments.

Imagine that we want to copy only the ๐Ÿ” and ๐ŸŒฎ from our previous example to a new array.

let favoriteFood = ["๐Ÿ•", "๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"];
let slicedArray = favoriteFood.slice(1, 3);
console.log(slicedArray); // ["๐Ÿ”", "๐ŸŒฎ"]

In the above example,

  • We have added index 1 as the first argument. Remember that the first argument includes the index itself when slicing the array.

  • As the second argument, we have added index 3. But it doesn't include the index when slicing the array. Instead, it only includes the elements up to that index. In this case, it will grab only up to index 2. This array slice returns a new array with ๐Ÿ” and ๐ŸŒฎ.

Another thing that we can do with the slice() method is that use negative numbers for arguments. Let's see how this works with the below example.

let favoriteFood = ["๐Ÿ•", "๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"];
let slicedArray = favoriteFood.slice(-3);
console.log(slicedArray); // ["๐Ÿ”", "๐ŸŒฎ", "๐Ÿจ"]

In the above example, we have added a single argument as -3. This will start counting from the end of the array and slice it (not the beginning of the array). If we have given -2, it will return only ["๐ŸŒฎ", "๐Ÿจ"]. This is very useful when you want to get the last element of the array, and then you just have to use -1.

The slice() method is very useful for cloning an array, copying a portion of an array, and converting an array-like object into an array.

splice()

The splice() method helps you add, update, and remove elements in an array. This method modifies the array and does not create a new array. It will also return a new array with all the elements you have removed, which is helpful if you want to track what has been removed.

The splice() method takes several arguments to decide which elements to delete, the delete count, and what elements to add. You can check further details on these parameters below.

Syntax:

// general
Array.splice(startIndex)
// With the optional parameters
Array.splice(start, deleteCount, newElement)

Parameters

  • start(required):

    • The index where the slice should begin for removing elements in the array.

    • If the start is negative, it will count backward from the end of the array.

  • deleteCount (optional):

    • The number of elements to be deleted from that index.

    • If you don't specify the deleteCount, it will delete everything in the array after the startIndex.

  • newElement (optional): The new element(s) to be added to the array.

Array.prototype.splice = function () {
  let returnResult = [];
  let start, deleteCount;
  let items = [];

  if (arguments.length === 0) {
    return returnResult;
  }

  // start
  start = arguments[0];
  if (start >= this.length) {
    return returnResult;
  }
  if (start < 0) {
    start = this.length + start;
  }

  // deleteCount
  if (arguments.length === 1) {
    deleteCount = this.length - start;
  }
  if (arguments.length >= 2) {
    deleteCount = arguments[1];

    if (deleteCount > this.length - start) {
      deleteCount = this.length - start;
    }
  }

  // items
  if (arguments.length > 2) {
    for (let i = 2; i < arguments.length; i++) {
      items.push(arguments[i]);
    }
  }

  // delete elements if delete count > 0
  if (deleteCount > 0) {
    for (let i = 0; i < deleteCount; i++) {
      returnResult.push(this[start + i]);
    }

    for (let i = start, j = 0; i <= this.length - deleteCount; i++, j++) {
      this[start + j] = this[start + j + deleteCount];
    }

    this.length = this.length - deleteCount;
  }

  // add elements if items are provided
  if (items.length > 0) {
    for (let i = this.length - 1; i >= start; i--) {
      this[i + items.length] = this[i];
    }

    for (let i = 0; i < items.length; i++) {
      this[start + i] = items[i];
    }
  }
  return returnResult;
};
// Test case 1:
const arr1 = ["a", "b", "c", "d", "e"];
const result1 = arr1.mySplice(2); 
console.log(arr1); // ['a', 'b']
console.log(result1); // ['c', 'd', 'e']

// Test case 2:
const arr2 = ["a", "b", "c", "d", "e"];
const result2 = arr2.mySplice(-2);
console.log(arr2); // ['a', 'b', 'c'] 
console.log(result2); //  ['d', 'e']

// Test case 3:
const arr3 = ["a", "b", "c", "d", "e"];
const result3 = arr3.mySplice(2, 1);
console.log(arr3); // ['a', 'b', 'd', 'e']
console.log(result3); // ['c']

// Test case 4:
const arr4 = ["a", "b", "c", "d", "e"];
const result4 = arr4.mySplice(2, 0, "A", "B");
console.log(arr4); 
// ['a', 'b', 'A', 'B', 'c', 'd', 'e']
console.log(result4); // []

// Test case 5:
const arr5 = ["a", "b", "c", "d", "e"];
const result5 = arr5.mySplice(2, 1, "A");
console.log(arr5);
// ['a', 'b', 'A', 'd', 'e']
console.log(result5); 
// ['c']

Let's see how to remove elements with a single argument, with only the start parameter.

let favoriteFruits = ["๐Ÿ“", "๐Ÿฅ‘", "๐ŸŠ", "๐Ÿ‡"];
let removedFruits = favoriteFruits.splice(2);
console.log(favoriteFruits); //  ["๐Ÿ“", "๐Ÿฅ‘"]
console.log(removedFruits); //   ["๐ŸŠ", "๐Ÿ‡"]

In the above example, we have added the start parameter as 2, and that's where it has started removing things from this array. Since we haven't specified a second parameter, it has removed everything after index 2, including the index 2 element. So now the favoriteFruits only includes ["๐Ÿ“", "๐Ÿฅ‘"]. And you can see the removed item in the array, removedFruits.

If you add 0 as the start parameter without any other parameters, it will remove everything from the array and change it to an empty array. Also, if you add any number higher than the largest index number of the array, it will not affect the original array.

So what happens if we add a negative number as the start parameter? If the start is negative, it will count backward from the end of the array and remove the elements. Check the below example.

let favoriteFruits = ["๐Ÿ“", "๐Ÿฅ‘", "๐ŸŠ", "๐Ÿ‡"];
let removedFruits = favoriteFruits.splice(-3);
console.log(favoriteFruits); //  ["๐Ÿ“"]
console.log(removedFruits); //   ["๐Ÿฅ‘", "๐ŸŠ", "๐Ÿ‡"]

In the above example, we have added the start parameter as -3. This will start counting from the end of the array and remove items. If we have given -2, the original array will return ["๐ŸŠ", "๐Ÿ‡"].

Now let's see how to remove elements with the start and deleteCount parameters.

Check the below example.

let favoriteFruits = ["๐Ÿ“", "๐Ÿฅ‘", "๐ŸŠ", "๐Ÿ‡"];
let removedFruits = favoriteFruits.splice(1, 2);
console.log(favoriteFruits); //  ["๐Ÿ“", "๐Ÿ‡"]
console.log(removedFruits); //   ["๐Ÿฅ‘", "๐ŸŠ"]

In the above example, we removed elements starting from index 1 and removed two elements. And it has modified the original array with the remaining elements and returned an array with the removed elements.

So let's move on to adding elements to the array with the newElement parameter.

You can add a continuous list of elements separated by commas. Let's add two additional fruits to our favorite Fruits.

let favoriteFruits = ["๐Ÿ“", "๐Ÿฅ‘", "๐ŸŠ", "๐Ÿ‡"];
let removedFruits = favoriteFruits.splice(1, 1, "๐Ÿ", "๐Ÿ’");
console.log(favoriteFruits); //  ["๐Ÿ“", "๐Ÿ", "๐Ÿ’", "๐ŸŠ", "๐Ÿ‡"]
console.log(removedFruits);  //   ["๐Ÿฅ‘"]

Let's see what we have done here:

  • We removed "๐Ÿฅ‘".

  • We set the deleteCount as 1 since we want to remove only one element.

  • And we added "๐Ÿ", "๐Ÿ’" to the array where we remove the elements.

We can add any number of elements to the array by separating them by commas. When we add elements to the array, the array will grow in length. Also, if you don't want to remove any items, you can simply add the second parameter as 0.

The splice() method is mainly used when you need to delete or add new elements to an array. And you can either assign the returned array to a variable or ignore it as you wish.

Now we have a clear idea about how slice() and splice() methods work. You can find out what're the main differences between these two methods below.

Slice() vs. Splice()

Slice()

Splice()

Does not modify the original array

Modifies the original array

This method is used to get a new array by selecting a sub-array of a given array.

This method is used to add/remove an item from the given array.

The result has to be assigned to a new array variable.

The result is not required to assign to a new variable.

Takes two arguments, both being optional.

Takes 3 arguments, the last two arguments being optional, and the last argument can have any number of parameters (just remember that you don't have to pass it as an array type).

The second argument represents an index.

The second argument represents the count.

concat()

concat() method merges two arrays or values.

  • Merging two arrays will return a new array.

  • Merging a variable into an array will merge variable into an array and return merged array.

  • Merging three arrays- Merges three arrays in given order and returns the merged array.

Array.prototype.myConcat = function () {
  let newArr = [];

  for (let i = 0; i < this.length; i++) {
    newArr.push(this[i]);
  }
  for (let i = 0; i < arguments.length; i++) {
    if (Array.isArray(arguments[i])) {
      const dummyArr = arguments[i];
      for (let i = 0; i < dummyArr.length; i++) {
        newArr.push(dummyArr[i]);
      }
    } else {
      newArr.push(arguments[i]);
    }
  }
  return newArr;
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];

// Test case 1:
const result1 = arr1.myConcat(arr2);
console.log(result1);
// output: [1, 2, 3, 4, 5, 6]

// Test case 2:
const result2 = arr1.myConcat("a");
console.log(result2);
// output: [1, 2, 3, "a"]

// Test case 3:
const result3 = arr1.myConcat(arr2, arr3);
console.log(result3);
// output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

flat()

flat() methods flatten an array up to a given depth. Flattens here means it concatenates the sub-array elements into the original array. It returns the flattened array.

  • no depth specified - flattens till depth 1

  • depth argument fixed- flattens till the given depth.

Array.prototype.flat = function (depth = 1) {
  const result = [];
  (function flatten(arr, depth) {
    for (let i = 0; i < arr.length; i++) {
      if (Array.isArray(arr[i]) && depth > 0) {
        flatten(arr[i], depth - 1);
      } else {
        result.push(arr[i]);
      }
    }
  })(this, depth);
  return result;
};
Array.prototype.deepFlat = function () {
  let result = [];
  (function flatten(arr) {
    for (let i = 0; i < arr.length; i++) {
      if (Array.isArray(arr[i])) {
        flatten(arr[i]);
      } else {
        result.push(arr[i]);
      }
    }
  })(this);
  return result;
};
const arr = [1, 2, 3, 4, 5];
// Test Case 1:
const arr1 = [10, 20, ["a", "b"]];
const result1 = arr1.myFlat();
console.log(result1);
// output: [10, 20, "a", "b"];

// Test Case 2:
const arr2 = [10, 20, [["a", "b"]]];;
const result2 = arr2.myFlat(2);
console.log(result2);
// output: [10, 20, "a", "b"];

flatMap()

flatMap() accepts a callback function. It executes the callback function on every element of the array, flattens the result to depth 1, and returns the newly formed array

Array.prototype.flatMap = function (cb) {
  let result = [];
  for (let i = 0; i < this.length; i++) {
    let cbRes = cb(this[i], i, this);
    if (Array.isArray(cbRes)) {
      for (let i = 0; i < cbRes.length; i++) {
        result.push(cbRes[i]);
      }
    } else {
      result.push(cbRes);
    }
  }
  return result;
};
const arr = [10, 21, 30, 42, 50];
// Test case 1: 
const result1 = arr.flatMap((el) => (el % 10 === 0 ? [] : [el]));
console.log(result1);
// output: [21, 42];

// Test case 2: 
const result2 = arr.flatMap((el) => el);
console.log(result2);
// output: [10, 21, 30, 42, 50];

join()

join() method concatenates the array and returns a string.

  • no seperator argument passed- concatenates element using a comma as a separator.

  • separator passed - concatenates elements using the seperator provided.

Array.prototype.myJoin = function (separator = ",") {
  let result = "";
  for (let i = 0; i < this.length; i++) {
    if (i === this.length - 1) {
      result += this[i];
    } else {
      result += this[i] + separator;
    }
  }
 return result;
};
const arr = [1, 2, 3, 4, 5];
// Test Case 1:
const commaSeperatedString = arr.myJoin();
console.log(commaSeperatedString);
// output: 1,2,3,4,5

// Test Case 2:
const result = arr.myJoin("*");
console.log(result);
// output: 1*2*3*4*5

Iterator Methods

keys()

keys() creates and returns an Array Iterator object with the key for each item in the array.

Array.prototype.keys = function () {
  let keys = [];
  for (let i = 0; i < this.length; i++) {
    keys.push(i);
  }
  function* iterator() {
    yield* keys;
  }
  return iterator();
};
const arr = ["a", "b", "c", "d", "e"];
const keys = arr.keys();
console.log(keys.next().value);
//output: 0
for (let key of keys) {
        console.log(key);
}
/**
output:
1
2
3
4
**/

values()

values() creates and returns an Array Iterator object with the value for each item in the array.

Array.prototype.values = function () {
  let keys = [];
  for (let i = 0; i < this.length; i++) {
    keys.push(this[i]);
  }
  function* iterator() {
    yield* keys;
  }
  return iterator();
};
const arr = ["a", "b", "c", "d", "e"];
const values = arr.values();
console.log(values.next().value);
//output: a
for (let value of values) {
        console.log(value);
}
/**
output:
a
b
c
d
e
**/

entries()

entries() creates and returns an Array Iterator object with key-value pairs for each item in the array.

Array.prototype.entries = function () {
  let keys = [];
  for (let i = 0; i < this.length; i++) {
    keys.push([i, this[i]]);
  }
  function* iterator() {
    yield* keys;
  }
  return iterator();
};
const arr = ["a", "b", "c", "d", "e"];
const entries = arr.entries();
console.log(entries.next().value);
//output: [0, 'a']
for (let entry of entries) {
        console.log(entry);
}
/**
output:
[1, 'b']
[2, 'c']
[3, 'd']
[4, 'e']
**/

Thanks for reading the article!!!

Happy Coding!

happy-coding.gif

ย