Functions in Javascript in Depth

If we are repeating certain lines of code over and over again, then we can use that code and convert it into a function. So today, we will be looking at what functions are in Javascript.

Like everything in Javascript, functions are also objects that have special internal properties that make them callable and/or constructable.

But if you use the typeof operator, it will return its type as a function.

function sum(a,b) {
  return a + b
}

typeof sum // function

typeof anyFunction —> function

Anatomy of a Function

Functions have different parts

  • Function Name
  • Function Body
  • Arguments
  • Parameters
  • Return Statement
  • Function Invocation

Arguments and parameters are used interchangeably but the difference is that parameters are just variables that are used in the function definition. Whereas arguments are actually values that are used when calling the function.

Functions can be defined in two ways.

  1. Function Declaration
  2. Function Expression

Function Declaration

Function declaration should start with the keyword function followed by the name of the function.

function sum(a,b) {
  return a + b;
}

const result = sum(4,6) // result is 10

Function Expression

In function expression, we define an anonymous function (a function without any explicit name) and assign it to the variable. When we want to invoke the function we use the variable name with the invoke brackets ( ).

const sum = function() {
  return a + b;
}

const result = sum(4,6) // result is 10

Difference between Function Declaration and Function Expression

The two approaches can be used to declare a function, but the two have a subtle difference in how the interpreter loads them.

Function declarations are loaded at the start before any code is executed but the function expressions are loaded when the interpreter reaches the line of code.

Function declarations are hoisted to the top of other codes. Function expressions aren’t hoisted, which allows them to retain a copy of the local variables from the scope where they were defined.

Arrow Functions

Arrow functions are introduced in ES6. It is also a way to define a function without using the function keyword and special thing is that it doesn't have its own this. It takes the values of its parent this.

const sum = (a, b) => {
  return a + b
}

const result = sum(4,6) // result is 10

Moreover, if the function body is only one line of code then we can skip the return keyword.

const sum = (a, b) => a + b

const result = sum(4,6) // result is 10

An arrow function doesn't even have access to arguments inside the body like normal functions have.

Other limitations include

  • Does not have its own bindings to this or super, and should not be used as methods.
  • Does not have arguments, or new.target keywords.
  • Can not be used as constructors.
  • Can not use yield, within its body.
  • Arrow functions do not have a prototype property.

Projecting in JS: When we extract some values of the data structure and make another data structure from those values then this is referred to as projecting in JS.

SideEffects

Side-effects are changes that take place outside the function scope. In functional programming, it is recommended to reduce the number of side-effects. But in web dev, we do it a lot of time like dom-manipulation which is also considered as a side-effect.

Examples of side effects:

  • Modifying any external variable or object property (e.g., a global variable, or a variable in the parent function scope chain)
  • Logging to the console
  • Writing to the screen
  • Writing to a file
  • Writing to the network
  • Triggering any external process
  • Calling any other functions with side-effects

Rest Parameters and Spread Operator

These two operators were introduced in ES6. Both these are denoted by triple dots ( . . .). The rest operator is used to grab all the remaining arguments and the spread operator is used to expand the iterable objects and arrays.

The spread operator comes in handy when copying the arrays or expanding the objects.

Rest Parameters

function sumAll(...args) { // args is the name for the array
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

console.log( sumAll(1) ); // 1
console.log( sumAll(1, 2) ); // 3
console.log( sumAll(1, 2, 3) ); // 6

Spread Operator

const arr = [1,2,3,4,5]
const arrCopy = [ ...arr ]

console.log(arrCopy) // [1,2,3,4,5]

const obj = {
  name: "Prince",
  country: "India"
}

const secondObj  = {
  isAlive: true,
  ...obj
}

console.log(secondObj) // {isAlive: true, name: "Prince", country: "India"}

Default Parameters

In ES6, we can simply set the default value of the parameter while defining a function in case it has not been specified during the function call. Before it was not possible and we have to check if the value exists using if-else or OR operator.

function sum(a , b = 2) {
  return a + b
}

console.log(sum(5,5)) // 10
console.log(sum(4)) // 6 

Arguments

In normal functions, we have access to arguments inside the function which can be quite useful. The arguments inside the function then come out to be array-like objects which are called pseudo-arrays. Arguments may be seen to be returned as an array but they are objects. They don't have access to array methods like .push and so on.

Moreover, you can't loop over the pseudo-array but they do have the length property. So, first, you need to convert it into a normal array and then you can use those methods.

Before ES6 you need to Array.prototype.slice.call(arguments). In ES6 we can use Array. from(arguments). These will convert pseudo-arrays to arrays.

function joinStr() {
  return arguments 
}

const res = joinStr("hello", "world", "again")

console.log(res) // ["hello", "world", "again"] length 3

In the above code, res seems to be an array that has a length of 3, but it is an object which looks like an array ie. it is a pseudo-array. Let us confirm using the following code.

function joinStr() {
  return arguments.join(' ') 
}

const res = joinStr("hello", "world", "again")

console.log(res) // SyntaxError

Now, when you call the above function, you will get a syntax error. Now let us convert it into an array and then see what happens.

function joinStr() {
  let argsArray = Array.prototype.slice.call(arguments);
  return argsArray.join(' ') 
}

const res = joinStr("hello", "world", "again")

console.log(res) // "hello world again"

This time it works. You could also use Array.from(arguments) instead of Array.prototype.slice.call(arguments) in the above code both will do fine.

Arguments in Arrow Functions

Arrow functions don't have access to arguments inside the function body. But that doesn't mean that you can't access the arguments inside the arrow function body.

const joinStr = () => { 
  return arguments
}

const res = joinStr("hello", "world", "again")

console.log(res) // Uncaught ReferenceError: arguments is not defined

As you can see we got arguments is not defined error, unlike the normal functions where we got the pseudo-arrays.

We can use a workaround and use the spread operator to access the arguments. Moreover, in this case, you don't need to convert it into an array.

const joinStr = (...args) => {
  return args.join(' ') 
}

const res = joinStr("hello", "world", "again")

console.log(res) // "hello world again"

This time we got the same output as using the normal functions.

Implementing _.from

const _ = {}

_.from = (iterable) => {
  return Array.prototype.slice.call(iterable);
}

function joinStr() {
  let argsArray = _.from(arguments) 
  return argsArray.join(' ') 
}

const res = joinStr("hello", "world", "again")

console.log(res) // "hello world again"

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