DEV Community

Cover image for Functions
Manav Misra
Manav Misra

Posted on • Updated on

Functions

Foreword

If you're completely new to functions, start here:

Overview

Functions is an incredibly deep topic for any programming language. Here, we just give some basic overview based on some of the most common ❓s that I receive from students.

Parameters and Arguments

When we see something like: function adder(num1, num2), num1 and num2 are parameters. Said parameters will be bound to arguments when we invoke/call/run the function. So, in the case of adder(1, 2), 1 would be bound to num1 and 2 would be bound to num2. This binding will endure throughout the function's scope - from the opening { to the closing {.

Variadic Parameter Functions

Sometimes, we wish to write a function that can accept an unlimited number of arguments. We can use rest parameters .... This will grab all of the _arguments and bind them as an array to the parameter.

// nums will be an array.
function addManyNumbers(...nums) {
  // TODO: Use 'reduce' to add up and return all the numbers
}

// Send as many arguments as you want
addManyNumbers(1, 2, 3, 4, 5)
Enter fullscreen mode Exit fullscreen mode

return

If you don't include a return in your function, it will implicitly return undefined. This is not generally desirable. AMAP, you should include an explicit return.

That return value could then be logged or just bound to another variable for later use.

function adder(num1, num2) {
  return num1 + num2
}

const sum = adder(1, 2);
Enter fullscreen mode Exit fullscreen mode

Arrow Syntax

As of ES2015/ES6+, we can save some typing. To create adder as we had mentioned πŸ‘†πŸ½:

const adder = (num1, num2) => {
  return num1 + num2
}
Enter fullscreen mode Exit fullscreen mode

If our function only has 1 statement, we can rely on an implicit return. This means we get rid of {, } and return, creating a beautiful '1-liner': const adder = (num1, num2) => num1 + num2 πŸ€“.

Default Parameters

We can give our parameters default values such that if we invoke the function without explicitly passing in a value, the default will be used.

Above, we first invoke w/o any arguments. So, both default values, 1 and 2 were used. In the second case, we did pass in our own value for the first parameter, 5. Only the second parameter used its default value, 2.

Methods

A method is nothing but a function that is scoped inside another object literal.

There are many built in methods that we use all of the time. For example, console.log(), where log is a function 'scoped' to the console object.

This can lead us to the basis of JS's prototypal OOP features - a topic for another post! πŸ˜…

Callback Functions

JS functions are first class πŸ†. Anything we can do with other data types, we can do with functions. We can pass functions as arguments into other functions - callback functions. We can also return functions from other functions.

Callbacks are great for 'event-driven' operations. For example, DOM interactions such as handling "click"s: addEventListener("click", () => {.

There is a function that gets 'called back' whenever the browser notifies JS of a 'click event.'

Closures - Returning a Function

In this pattern, we are utilizing a function factory 🏭. It's a function that returns other functions. Along the way, the argument passed in to create that 'returned function' becomes enclosed, with a bound reference that persists as long as that 'created function' persists.

Closures is a tough concept to learn about. The below example usually does the trick in my classes to illustrate the pattern at least:

'Pure' Functions

This too could be a whole separate topic, but generally, 'pure functions' are those that:

i) given the same input always return the same output
ii) Have explicit return
iii) Don't depend on anything outside of their scope.

I liken these to 'copy/paste' functions. If they are written correctly, these functions could be copied and pasted into any codebase and invoked w/o any issue. They are 'independent' functions.'

Don't overthink 🧠 this. const adder = (num1, num2) => num1 + num2 is a pure function. We can copy/paste it anywhere and it doesn't care about anything outside of its scope. It just sits around until we send it 2 numbers. Then it does its job w/o touching anything else in our program πŸ‘πŸ½.

🎢 about some 'Best Practices' and 'Clean Code'

I'm going to highlight a couple of the recommendations given here:

  1. Functions should do 1 thing. If you can't clearly name your function, then chances are it's doing too much. Regarding naming, I personally try to start the name with a verb to make it clear that it's a function and that it 'does' something.
  2. Functions should avoid taking more than 2 parameters. If you need more, rely on object destructuring. Here's an example:

We invoke the function by passing in an object literal as the argument. This is then destructured. You can see that we can still provide default values as previously discussed πŸ‘†πŸ½.

You might also notice that our parameter as a whole has a default value, {} πŸ€”. I'll leave it as an exercise for you to deduce why we might want that. Hint: Try πŸƒπŸ½β€β™‚οΈ the function w/o any arguments with and w/o that {} default.


There's a lot more that could be said on this topic, but after a while too much theory is counterproductive. You should go practice writing code πŸ‘©πŸ½β€πŸ’» while keeping these things in mind 🧠. Then, come back and post your ❓s for discussion. You have more than enough to start writing ✍️ your own functions.

Top comments (0)