Javascript Objects and Arrays in Depth

Functional Programming

Functional programming a way of writing code and architecting our application in which we treat everything as functions.

Functional programming is about verbs and we think in terms of actions whereas in oops we think in terms of the noun. The advantage of functional programming over oops is that we can write pure functions without side effects. But if you are working with the web, then side effects are sure to happen like dom manipulation is a side effect.

Side effects occur when something outside the function gets affected. Moreover, the code is easy to test and debug when using a functional programming paradigm.

Examples of libraries that use the functional programming paradigm are lambda, rambda, lodash, etc.

Objects and Arrays

There are two types of values

  • Primitive values - string, number, boolean, null, undefined.
  • Non Primitive values - basically everything else.

Primitive values are passed by value means that every time a new copy is made in the memory whereas Non Primitive values are passed by reference means that they are not copied they are referenced. They are accessed by using a pointer, and if you change any data, then it will get updated in all the places where they are used. So it is recommended not to mutate the data structures directly to avoid side effects. Moreover, you should first make a copy of the data structure and then mutate and return it.

Anything that uses dot notation is an object in js. Arrays are also objects with some special built-in methods like .push, .pop.

var person = []

typeof person === "array" ---> return false
typeof person === 'object' ---> return true

person.name = "John"
var second = person.name // second is now "John"

typeof second === 'array' ---> return false
typeof second === 'object' ---> return true

As seen in the case above even though we have defined a person as an Array. But when we check its type of then it returns an object. It is wired but everything in js is an object apart from primitive values.

But if you want to check if some data structure is an array then javascript offers a function Array.isArray() to check if the data structure is an array.

var person = []

Array.isArray(person) ---> return true

An array is a special type of object which have some methods attached to it and has special .length property.

Dot Notation and Bracket Notation

For accessing the values and inserting the values in an array or object we have two types of notations.

var y = []

y[0] = "hey" // y is now ["hey"]

y.hello = "world"

// y will now be
[
  0: "hey",
  "hello": "world"
]

The basic difference between the two is that dot notation only works with strings. When we use dot notation the next part to dot gets type coerced to a string.

var y = []

y[0] = 25

y["name"] = "Prince"

y.hello = "world"

// array y is now
[
  0: 25,
  "name": "prince",
  "hello" : "world",
]

However, we also can't use explicitly use a string like we can't type y."surname". If we do use it, then the compiler throws a syntax error.

y."surname" // throws syntax error unexpected string

If we use a number as a string using brackets notation then it automatically gets coerced to a number.

y["1"] = "sumberia"

// array y will now become
[
  0: 25,
  1: "sumberia",
  "name": "prince",
  "hello" : "world"
]

But the problem with the above is that if we check the .length property on the y array then something strange will happen. The length will come out to be 2.

y.length // returns 2

This happens because the length only considers numerical indices for calculating the length property. It skips the indices that are not numerical.

Moreover when we define numbers in the dot notation then no matter what the indices are present or not it will still refer to the lower indices as empty.

y.length // returns 2

y[45] = "world"

y.length // returns 45

In the above case, the other indices which are not defined will be empty.

In the case of brackets notation, we can use a variable, almost anything and if it is not a variable, expression, number, or IIFE then they get coerced into strings.

y[function(){}] = "hello world"

// array y will now be
[
  0: 25,
  1: "sumberia",
  .
  .
  45: "world",
  "name": "prince",
  "hello" : "world",
  "function(){}" = "hello world"
]

The above function gets coerced into a string because it has not been immediately called.

y[(function(){return 2})()] = "hello world again"

// array y will now be
[
  0: 25,
  1: "sumberia",
  2: "hello world again",
  .
  .
  45: "world",
  "name": "prince",
  "hello" : "world",
  "function(){}" = "hello world"
]

In the above case, we have immediately called the function and it returns 2. So two will become the index.

Lastly, if you have defined an object we can you dot notation or even brackets notation. Both will work just fine.

var x = {}
x.name = "prince"

// x will not be
{
  "name" : "prince"
}

x[0] = "sumberia"

// x will now be
{
  "name" : "prince",
  0: "sumberia"
}

The special way that an array works with numerical indices is the reason why it works well for sorting, reversing, etc. Numerical indices and .length property make it much easy.

Brackets Notation works with

  • strings
  • variables
  • numbers
  • expressions
  • wired characters
  • IIFEs

Dot Notation works with

  • strings

Destructuring

Destructuring is the new concept that has been introduced in ES6. What it does is allows destructuring the properties of the object or the array without using dot or brackets notation. It just saves time. It just creates the variables and assigns the values to them.

The left side is the target variable names to which the right side source should be destructured. This means on the left side we are declaring the variables and on the right side, there will be a source array or object.

// Arrays
//  =target=    =source==
const [a , b] = [2 , 3]

// values of a and b
a = 2
b = 3

// Objects
const {firstName, lastName} = { firstName:"prince", lastName:"sumberia"}

// values of firstName and lastName will be'
firstName = 'prince'
secondName = 'sumberia'

Destructuring in arrays and objects works differently. For objects, the name of the target variables should match as the objects don't maintain their property order. Whereas we can name the target variables as we like, it doesn't necessarily be the same name.

In the case of the objects, the destructuring takes place while referring to the property name, while for arrays, it occurs using the order.

const [first, second] = ["hello", "world"]

// first and second will be
first = "hello"
second = "world"

const {lastName, firstName} = { firstName:"prince", lastName:"sumberia"}

// values of firstName and lastName will be'
firstName = 'prince'
secondName = 'sumberia'

Moreover, swapping variables values with destructuring comes in super handy.

a = 1
b = 2

const [b,a] = [a,b]

// now the variables will be
a = 2
b = 1

Destructuring in Nested Arrays and objects

It also works with nested arrays and objects.

const [a, [b,c], d] = [2,[3,4],[4,5,6]]

// the output will be

a = 2
b = 3
c = 4
d = [4,5,6]

Destructuring nested arrays and objects can be a little tricky. So, make sure to keep an eye on it.

const [a, b, c] = [1,[2,[3,4]],[5,6,7]]

// output
a = 1
b = [2,[3,4]]
c = [5,6,7]

Note that in the above example, the three variables are assigned based on the main array. Now if you want o access the next values then, you have to make the same structure on the target side to get all the values. For example, you could do the following:

const [a, [b,[c,d]], [e,f,g]] = [1,[2,[3,4]],[5,6,7]]

// output
a = 1
b = 2
c = 3
d = 4
e = 5
f = 6
g = 7

One thing to note about destructuring is that it will only destruct the values from the source array or object that are defined on the target side. And this is true for nested arrays and objects as well. Of course, you can get the remaining values using the spread operator as mentioned in the next section.

const [a] = [2 , 3]
// it will destruct only one value
a = 2

const {lastName} = { firstName:"prince", lastName:"sumberia"}
// it will only destruct lastName property
lastName = "sumberia"

If you want to skip values while destructuring arrays, you can do it by using _

const [a, _ , b] = [2 , 3, 4]
// in this case the second values that is 3 will be skipped

// output
a = 2
b = 4

Spread Operator

Another great feature introduced in ES6 is the spread operator. It comes in handy when copying the entire array or when working with restructuring. This is represented with just three simultaneous dots.

const arr = [2,3,4]

const copyArr = [...arr]
// the copyArr will be
[2,3,4]

Using with destructuring

It comes in handy in several ways when used with destructuring. It helps to copy the items in the array if don't know the number of items in the array.

const arr = [1,2,3,4,5,6,7,8,9]

const [a,b,c,...d] = arr
// the values of the four variables will be
a = 1
b = 2
c = 3
d = [4,5,6,7,8,9]

let {first, ...others} = { first : 1, second : 2, third : 3}
// output
first = 1
others =  { second : 2, third : 3}

As you can see the last variable with which we have used the spread operator will be an array with all the remaining values.

Looping in Objects and Arrays

There are several ways to loop over arrays and objects, in this section we will look at some of the most fundamentals ways.

For objects, we will use for in loop and for arrays, we will use simple for loop. The other fancy loops are just created using these loops.

For Loop

const arr = ["hello", "world", "this", "is", "prince"]

for (var i=0 ; i < arr.length; i++){
  console.log(arr[i]);
}

// Output
"hello world this is prince'

For in Loop

const obj = {
  0 : "hello",
  1 : "world",
  2 : "this",
  3 : "is",
  4 : "prince"
}

for (let key in obj) {
  console.log(obj[key]);
}

// Output
'hello world this is prince'

Hydration is a process of transforming data into a data structure. For example to transform JSON data to class-based data or arrays. Classes in JS are just functions that return objects.

Implementing _.each

We will be implementing _.each using for loop. The problem with native forEach is that it works only with arrays. And in that case, for looping objects, we had to transform objects into arrays. Our implemented version will work will objects as well as arrays.

There are many versions of each like native forEach and each in different functional libraries like lodash, underscore, etc. But basically, all these work the same way.

const _ = {}; // declare an empty object from which we can call _.each method

_.each = (list, callback) => {
  if (Array.isArray(list)) {
    for (let i = 0; i < list.length; i++) {
      callback(list[i], i, list);
    }
  } else {
    for (let key in list) {
      callback(list[key], key, list);
    }
  }
};

_.each(["hello", "world"], function (name, i) {
  console.log(name.toUpperCase());
});
// Output
// HELLO
// WORLD

_.each({ name: "hello", surname: "world" }, function (a, i) {
  console.log(a.toUpperCase());
});
// Output
// HELLO
// WORLD

Difference between each and map is that each doesn't return an array, but the map does and its length is the same as the source array.

Implementing _.map

const _ = {}

_.map = (list, callback) => {
  const result = []
  if (Array.isArray(list)) { // for arrays
    for (var i = 0; i < list.length; i++) {
      result.push(callback(list[i], i, list));
    }
  } else {
    for(let key in list) { // for objects
      result.push(callback(list[key], key, list));
    }
  }
  return result;
}

const list = [1, 2, 3]
const obj = {0 : 3, 1 : 6, 2 : 9}

const res =  _.map(list, function(item){ return item * 2 })
const resobj =  _.map(obj, function(item){ return item * 2 })

console.log(res) // returns [ 2, 4, 6 ]

console.log(resObj) // returns [ 6, 12, 18 ]

Implementing _.map using _.each

const _ = {}

_.each = (list, callback) => {
  if (Array.isArray(list)) {
    for (let i = 0; i < list.length; i++) {
      callback(list[i], i, list);
    }
  } else {
    for (let key in list) {
      callback(list[key], key, list);
    }
  }
};

_.map = (list, callback) => {
  const result = []
  const res = _.each(list, function(item, i , list){
    result.push(callback(item, i, list))
  })
  return result;
}

const list = [1, 2, 3]
const obj = {0 : 3, 1 : 6, 2 : 9}

const res =  _.map(list, function(item){ return item * 2 })
const resobj =  _.map(obj, function(item){ return item * 2 })

console.log(res) // returns [ 2, 4, 6 ]

console.log(resObj) // returns [ 6, 12, 18 ]

Implementing _.filter

The filter is another useful method in case we are working with array and objects. In case, we want to filter values from the array. Moreover, the callback we pass into the filter method should return a boolean. If the condition in the callback returns true for the item then it will be in the resulting array.

const _ = {}

_.filter = (list, callback) => {
  const arr = []
  if(Array.isArray(list)) {
    for (var i = 0; i < list.length; i++) {
      if (callback(list[i], i, list)) {
        arr.push(list[i])
      }
    }
  } else {
    for (let key in list) {
      if (callback(list[key], key, list)) {
        arr.push(list[key])
      }
    }
  }
  return arr;
}

const list = [1, 2, 3, 4]
// const list = {0:3,1:2,2:6, 3:9}

const res =  _.filter(list, function(item){ return item % 2 === 0 })
console.log(res) // return [2,4]

Implementing _.filter using _.each

const _ = {}

_.each = (list, callback) => {
  if (Array.isArray(list)) {
    for (let i = 0; i < list.length; i++) {
      callback(list[i], i, list);
    }
  } else {
    for (let key in list) {
      callback(list[key], key, list);
    }
  }
};

_.filter = (list, callback) => {
  const arr = []
  _.each(list, function(item, i, list) {
    if (callback(item, i, list)) {
      arr.push(item)
    }
  })
  return arr
}

const list = [1, 2, 3, 4]
// const list = {0:3,1:2,2:6, 3:9}

const res =  _.filter(list, function(item){ return item % 2 === 0 })
console.log(res) // return [2,4]

Implementing _.flatMap() using _.map and _.filter

_.flatMap() is a useful method that lets us skip items during the map method. This comes very handy when we want to manipulate the data items and filter unwanted values simultaneously. It is a very useful method when working with API and JSON data.

What it does is that it first map over the array and the returned array is flattened using the .flat() method. And then the array is filtered for the falsy values according to the condition mentioned in the callback function and returns the item if the item satisfies the condition in the callback.

We can define the callback as such that it returns an empty array [ ] or null for the items that we don't want in the resulting array and return items or manipulated array that we want in the resulting array.

const _ = {}

_.each = (list, callback) => {
  if (Array.isArray(list)) {
    for (let i = 0; i < list.length; i++) {
      callback(list[i], i, list);
    }
  } else {
    for (let key in list) {
      callback(list[key], key, list);
    }
  }
};

_.map = (list, callback) => {
  const result = []
  const res = _.each(list, function(item, i , list){
    result.push(callback(item))
    })
    return result;
}

_.filter = (list, callback) => {
  const arr = []
  _.each(list, function(item, i, list) {
    if (callback(item, i, list)) {
      arr.push(item)
    }
  })
  return arr
}

_.flatMap = (list, callback) => {
  const arr = []
  const mappedArr = _.map(list, callback)
  const filteredArr = _.filter(mappedArr.flat(), function(item) {
    return item
  })
  return filteredArr
}

let a = [5, 4, -3, 20, 17, -33, -4, 18]

const res = _.flatMap(a, function(item) {
  if (item > 0) {
    return item
  } else {
    return null
  }
})

// Output : [5, 4, 20, 17, 18]

const res2 = _.flatMap(a, (n) => {
  if(n < 0) {
    return []
  }
  return (n % 2 == 0) ? [n] : [n-1, 1]
})

// Output : [4, 1, 4, 20, 16, 1, 18]

Resources:

That's all for this post. If you enjoyed and found this article helpful then a like or share is highly appreciated.