Unleashing the Power of Destructuring in JavaScript

Unleashing the Power of Destructuring in JavaScript

Destructuring is a powerful power feature in Javascript that allows us to extract values from arrays, objects, Sets and Maps.

Javascript also provides advanced features such as Spread and Rest operators, which can be used to manipulate arrays and objects more efficiently.

Moreover destructuring can also be achieved using Set and Map objects in Javascript. Set and Map are not directly considered as destructuring, they do use similar syntax to the spread operator, which is a useful tool for working with iterable data types.


The above four properties are discussed below :

Spread

The spread(...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

It can be understood with the help of the code below :

const person {name: "Alex", age: 30};
const address {city: "New York", country: "USA"};

Let's create a new object that contains both the properties of "person" and "address"

const mergedObj = { ...person, ...address };
// spreading the properties of both "person" and "address" into a new object "mergedObj"

This results in

{
  name: "Alex",
  age: 30,
  city: "New York",
  country: "USA"
}

Spread syntax can be used when all elements from an object or array need to be included in a new array or object, or should be applied one by one in a function call's arguments list. Three distinct places that accept the spread syntax are:

  • Function arguments list (myFunction(a, ...iterableObj, b))

  • Array literals ([1, ...iterableObj, "five", 4])

  • Object literals ({...obj, key: "value"})

Spread can be used in various ways to manipulate arrays :

  1. Concatenation: Concatenation of arrays is generally achieved by the "concat()" method, but with it can also be achieved with the help of spread.
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const newArr = [...arr1, ...arr2];

console.log(newArr); // Output: [1, 2, 3, 4, 5, 6]
  1. Cloning arrays: "slice()" method is used for shallow copying of arrays but with the help of spread it looks something like this:

     const arr1 = [1, 2, 3];
     const arr2 = [...arr1];
    
     console.log(arr2); // Output: [1, 2, 3]
    
  2. Merging objects: "Object.assign()" method is used to merge objects into a new object. We can also achieve the same result using spread.

const obj1 = { name: 'Alex', age: 30 };
const obj2 = { city: 'New York', country: 'USA' };
const mergedObj = { ...obj1, ...obj2 };

console.log(mergedObj); // Output: { name: 'Alex', age: 30, city: 'New York', country: 'USA' }

Overall the spread syntax provides a convenient and concise way to manipulate arrays.


Rest

The rest parameter syntax is a way of representing vardiac functions(indefinite arguments as an array) in Javascript.

Syntax :

function f(a, b, ...theArgs) {
  // …
}

Note: A function definition can have only one rest parameter and it should be placed at last.

Using Rest Parameters :

function myFun(a, b, ...manyMoreArgs) {
  console.log("a", a);
  console.log("b", b);
  console.log("manyMoreArgs", manyMoreArgs);
}

myFun("one", "two", "three", "four", "five", "six");

// a, "one"
// b, "two"
// manyMoreArgs, ["three", "four", "five", "six"] <-- an array

...manyMoreArgs is the Rest parameter here.

Argument length :

function fun1(...theArgs) {
  console.log(theArgs.length);
}

fun1(); // 0
fun1(5); // 1
fun1(5, 6, 7); // 3

Using parameters in combination with an ordinary array :

function multiply(multiplier, ...theArgs) {
  return theArgs.map((element) => multiplier * element);
}

const arr = multiply(2, 15, 25, 42);
console.log(arr); // [30, 50, 84]

Here the first element of the array is used to multiply the rest element.

From arguments to array :

Array methods can be used in rest parameters but not on argument objects.

function sortRestArgs(...theArgs) {
  const sortedArgs = theArgs.sort();
  return sortedArgs;
}

console.log(sortRestArgs(5, 3, 7, 1)); // 1, 3, 5, 7

function sortArguments() {
  const sortedArgs = arguments.sort();
  return sortedArgs; // this will never happen
}

console.log(sortArguments(5, 3, 7, 1));
// throws a TypeError (arguments.sort is not a function)

Rest parameters are useful when you want to create a function that can accept a variable number of arguments without having to explicitly define each argument. This makes your code more flexible and easier to maintain.

One common use case for rest parameters is when you want to create a function that can operate on an arbitrary number of elements in an array. For example, suppose you have an array of numbers and you want to write a function that can calculate the sum of all the numbers in the array:

function sumArray(numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sumArray([1, 2, 3, 4])); // Output: 10

But what if we get an array of unknown sizes:

function sumArray() {
  var numbers = Array.prototype.slice.call(arguments);
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sumArray.apply(null, [1, 2, 3, 4])); // Output: 10

"apply()" method is used to tackle the situation.

With rest, it becomes easy to read and is less cumbersome.

function sumArray(...numbers) {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sumArray(1, 2, 3, 4)); // Output: 10

Set

The Set objects let us store any type whether it is primitive values or object references.

Creating a Set:

The "Set()" constructor is used to create a new set. Alternatively, we can pass any iterable such as an array to the "Set()" constructor.

const mySet = new Set([1,2,3]);

Methods in a set:

  • add(value): Add elements to a Set.

  • delete(value): Remove elements from a Set.

  • has(value): It is used to check if a particular element is present in a Set and returns a boolean value.

  • keys(): Returns a new iterator object that contains the keys for each element in the Set.

  • entries(): Returns a new iterator object that contains an array of [value, value] for each element in the Set (since Set objects don't have keys).

  • clear(): Removes the entire element from the Set.

  • values(): Returns a new iterator object that contains the values of each element in the Set.

  • forEach(callbackFn, thisArg): Calls the given function for each element in the Set, in insertion order. The second argument "thisArg" is optional and sets the value of this within the callback function.

  • size(): Return the number of elements in the Set.

const mySet = new Set();

// add(value)
mySet.add('apple');
mySet.add('banana');
mySet.add('cherry');
console.log(mySet); // Set { 'apple', 'banana', 'cherry' }

// clear()
mySet.clear();
console.log(mySet); // Set {}

// add(value) again
mySet.add('apple');
mySet.add('banana');
mySet.add('cherry');

// delete(value)
mySet.delete('banana');
console.log(mySet); // Set { 'apple', 'cherry' }

// has(value)
console.log(mySet.has('cherry')); // true
console.log(mySet.has('orange')); // false

// size
console.log(mySet.size); // 2

// keys()
const keysIterator = mySet.keys();
console.log(keysIterator.next()); // { value: 'apple', done: false }
console.log(keysIterator.next()); // { value: 'cherry', done: false }
console.log(keysIterator.next()); // { value: undefined, done: true }

// values()
const valuesIterator = mySet.values();
console.log(valuesIterator.next()); // { value: 'apple', done: false }
console.log(valuesIterator.next()); // { value: 'cherry', done: false }
console.log(valuesIterator.next()); // { value: undefined, done: true }

// entries()
const entriesIterator = mySet.entries();
console.log(entriesIterator.next()); // { value: [ 'apple', 'apple' ], done: false }
console.log(entriesIterator.next()); // { value: [ 'cherry', 'cherry' ], done: false }
console.log(entriesIterator.next()); // { value: undefined, done: true }

// forEach(callbackFn, thisArg)
mySet.forEach((value, key, set) => {
  console.log(value, key, set);
});
// apple apple Set { 'apple', 'cherry' }
// cherry cherry Set { 'apple', 'cherry' }

For "keys", "values" and "entries" methods, we can also use the Spread operator (...) to convert Set into an array.

const mySet = new Set(['apple', 'banana', 'cherry']);

// Using the spread operator to convert the Set to an array
const myArray = [...mySet];
console.log(myArray); // [ 'apple', 'banana', 'cherry' ]

Iterating sets:

In Javascript, we can iterate over the elements of a Set using various iteration methods ("keys", "values" and "entries")

One way to do this using "for...of" loop or the "next()" method.

const mySet = new Set(['apple', 'banana', 'cherry']);

// Using for...of loop to iterate over Set
for (const fruit of mySet) {
  console.log(fruit);
}

//
//
//
const mySet = new Set(['apple', 'banana', 'cherry']);

// Using the keys() method
const keysIterator = mySet.keys();
for (const key of keysIterator) {
  console.log(key); // apple, banana, cherry
}

// Using the values() method
const valuesIterator = mySet.values();
for (const value of valuesIterator) {
  console.log(value); // apple, banana, cherry
}

// Using the entries() method
const entriesIterator = mySet.entries();
for (const entry of entriesIterator) {
  console.log(entry); // [ 'apple', 'apple' ], [ 'banana', 'banana' ], [ 'cherry', 'cherry' ]
}

//
//
//
//using next()
const mySet = new Set(['apple', 'banana', 'cherry']);

// Using entries() method to get iterator for Set
const entries = mySet.entries();

// Using while loop to iterate over the Set
let result = entries.next();
while (!result.done) {
  console.log(result.value);
  result = entries.next();
}

Examples using Set:

// BASIC SET OPERATION
function isSuperset(set, subset) {
  for (const elem of subset) {
    if (!set.has(elem)) {
      return false;
    }
  }
  return true;
}

function union(setA, setB) {
  const _union = new Set(setA);
  for (const elem of setB) {
    _union.add(elem);
  }
  return _union;
}

function intersection(setA, setB) {
  const _intersection = new Set();
  for (const elem of setB) {
    if (setA.has(elem)) {
      _intersection.add(elem);
    }
  }
  return _intersection;
}

function symmetricDifference(setA, setB) {
  const _difference = new Set(setA);
  for (const elem of setB) {
    if (_difference.has(elem)) {
      _difference.delete(elem);
    } else {
      _difference.add(elem);
    }
  }
  return _difference;
}

function difference(setA, setB) {
  const _difference = new Set(setA);
  for (const elem of setB) {
    _difference.delete(elem);
  }
  return _difference;
}

// Examples
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 3]);
const setC = new Set([3, 4, 5, 6]);

isSuperset(setA, setB); // returns true
union(setA, setC); // returns Set {1, 2, 3, 4, 5, 6}
intersection(setA, setC); // returns Set {3, 4}
symmetricDifference(setA, setC); // returns Set {1, 2, 5, 6}
difference(setA, setC); // returns Set {1, 2}

//
//
//
//RELATION TO ARRAYS
const myArray = ["value1", "value2", "value3"];

// Use the regular Set constructor to transform an Array into a Set
const mySet = new Set(myArray);

mySet.has("value1"); // returns true

// Use the spread syntax to transform a set into an Array.
console.log([...mySet]); // Will show you exactly the same Array as myArray
//
//
//
//Use to remove duplicate elements from an array

const numbers = [2, 3, 4, 4, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 5, 32, 3, 4, 5];

console.log([...new Set(numbers)]);

// [2, 3, 4, 5, 6, 7, 32]

In conclusion, a Set in JavaScript is a collection of unique values. It provides various methods to add, delete, and manipulate values. The Set object is iterable, which means you can use a loop to iterate through its elements. Additionally, the keys, values, and entries methods can be used to extract specific information from a Set. Overall, the Set is a useful tool for managing collections of data in JavaScript.


Map

The "map()" in JavaScript is a built-in method that is available on arrays. It creates a new array by executing a provided function on every element of the original array. The resulting array has the same length as the original array, with each element replaced by the result of the provided function.

The syntax is as follows:

// Arrow function
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })

// Callback function
map(callbackFn)
map(callbackFn, thisArg)

// Inline callback function
map(function (element) { /* … */ })
map(function (element, index) { /* … */ })
map(function (element, index, array) { /* … */ })
map(function (element, index, array) { /* … */ }, thisArg)

Examples using map:

const numbers = [1, 4, 9];
const roots = numbers.map((num) => Math.sqrt(num));

// roots is now     [1, 2, 3]
// numbers is still [1, 4, 9]
//
//
//
const kvArray = [
  { key: 1, value: 10 },
  { key: 2, value: 20 },
  { key: 3, value: 30 },
];

const reformattedArray = kvArray.map(({ key, value }) => ({ [key]: value }));

console.log(reformattedArray); // [{ 1: 10 }, { 2: 20 }, { 3: 30 }]
console.log(kvArray);
// [
//   { key: 1, value: 10 },
//   { key: 2, value: 20 },
//   { key: 3, value: 30 }
// ]