DEV Community

Cover image for Tricky JS: What every Software Developer must know about Functions
danielAsaboro
danielAsaboro

Posted on • Updated on

Tricky JS: What every Software Developer must know about Functions

Functions are not just crucial building blocks in JavaScript; they are the heartbeat of the language itself - as they form the basis of modular, reusable code.

This is what lets developers abstract complex program logic into manageable chunks to solve problems efficiently. The thing is, everyone knows and understands this.

But only a few software developers have been able to master them.

It's not that it's as difficult like rocket science, solving a Rubik's Cube blindfolded, or untangling a nest of headphone cables. It's just that they can be quite tricky to grasp.

A few examples in the wild:

The "this" keyword in JavaScript functions can be a perpetual challenge. This is because its value depends on how the function is invoked, and this can vary in different contexts.

Understanding when the "this" keyword:

  • refers to the global object,
  • the object that owns the function, or
  • when it's explicitly bound, requires careful consideration.

Failure to grasp these nuances can result in unexpected errors and undesired behavior. Let's not even get to the nuances of variable hoisting or how JavaScript's support for closures — which allow functions retain access to variables from their outer scope, can be a double-edged sword...

Yet, understanding these little subtleties and mastering them is how young budding developers can create elegant, efficient, and maintainable solutions in JavaScript while opening a new realm of possibilities.

Demystifying JavaScript Functions: Understanding Their True Nature

In their basic form, functions are reusable blocks of code that can be defined and invoked (executed) many times to perform specific tasks. Let's take a pause and break that down.

  1. First, they must be defined,
  2. Then, they can be Invoked (as many times as possible).

And this is where it gets tricky!

How you define your functions determines how they behave and how they can be invoked. This is what this article aims to clarify.

When you are done reading, you should:

  1. Know the fundamental ways of defining functions.
  2. Understand how to define and call them correctly.
  3. Know how to define and work with multiple parameters, including understanding their order and specifying default values if needed.
  4. Understand the scope of variables within functions,
  5. Learn to abstract and encapsulate logic into functions to promote modularity and maintainable code so that you can write better efficient code.

How to Define a Function

There are three fundamental ways to define functions in Javascript. They are:

  1. Function Declaration.
  2. Function Expressions.
  3. Arrow Functions.

Each has its strengths and drawbacks.

Instead of leaving you to spend valuable time breaking things to learn how they work and Interact, I will give you a brief overview of each so that you know when to use them and when not to.

A. Function Declaration

This is the most straightforward approach to defining functions. It's difficult to make mistakes when following it. This is also why it's the first method beginners are introduced to.

Here's an example.

  // This function greets the person with the given name.
function greet(name) {

  console.log("Hello, " + name + "! How are you today?");
}
Enter fullscreen mode Exit fullscreen mode

Here's another.

     // This function calculates the area of a rectangle given its length and width
function calculateArea(length, width) {

    var area = length * width;
    return area;
}

Enter fullscreen mode Exit fullscreen mode

The syntax is plain and simple:

  • There's always a Name: It's a required part of every function declaration. A variable is created in memory with that same name and the newly defined function object is assigned to it.
  • A pair of parenthesis: Inside them is a comma-separated list of parameters. Sometimes though, it could be empty i.e Zero Parameters. This parameters are, however, scoped to the body of the function (i.e Local Variables).
  • And a pair of Curly Braces: Enclosed in these pair of curly braces are are sets of JavaScript statements. They could be zero, but as far as I'm concerned, that's pointless. It's this states that get executed whenever the function is called (invoked).
// Compute the distance between Cartesian points (x1,y1) and (x2,y2).
function distance(x1, y1, x2, y2) {
   let dx = x2 - x1;
   let dy = y2 - y1;
   return Math.sqrt(dx*dx + dy*dy);
}
Enter fullscreen mode Exit fullscreen mode

B. Function Expressions

Function expressions are not that different from function declarations. They have the function tag to show they are a function, a set of parenthesis, and a pair of curly braces.

The difference that jumps right out to you is, Function Expressions don't stand alone. Instead, they are often part of a larger Javascript statement.

As a result, the function name is optional.

So this is valid function…

  // This function greets the person with the given name.
const greet = function (name) {
  console.log("Hello, " + name + "! How are you today?");
}
Enter fullscreen mode Exit fullscreen mode

So also is this...

const calculateAread = function (length, width) {
    var area = length * width;
    return area;
}
Enter fullscreen mode Exit fullscreen mode

Another difference, but an under-the-hood operation is, a function declaration initializes a variable and assigns a function object to it. While a function expression doesn't.

This is why you will always see them assigned to a constant.

Var would work, but it's best practice to use const so that you don't overwrite them accidentally with new values.

Beside that, Function Expressions are not hoisted.

Unlike Function Declaration, they don't exist until their expressions are evaluated. This means they must be defined before they are called.

As a result, this would throw an error!

myFunc(); // Error: myFunc is not a function

var myFunc = function() {
  console.log("This is a function expression");
};
Enter fullscreen mode Exit fullscreen mode

But this wouldn't :)

myFunc(); // Error: myFunc is not a function

function myFunc() {
  console.log("This is a function expression");
};

Enter fullscreen mode Exit fullscreen mode

You can't can’t refer to a function defined as an expression until it is assigned to a variable. So you must write function expressions in the same order as the program flow:

  • Function Definition, then
  • Function Invokation (calling).

C. Arrow Functions

Arrow functions are a concise way to write functions in JavaScript. The => symbol replaces the need for the function keyword, while still maintaining the ability to accept parameters.

As a result, they shrink the lines of code you have to write.

// Traditional Function
function greet(name) {
  return "Greetings, " + name + "!";
}

// Arrow Function
const greet = (name) => {
  return "Greetings, " + name + "!";
};

Enter fullscreen mode Exit fullscreen mode

If the function body consists of a single expression, we can further streamline the process:

// Arrow Function with Implicit Return
const double = (number) => number * 2;

// Arrow Function without Parameters
const getRandomNumber = () => Math.random();
Enter fullscreen mode Exit fullscreen mode

This is what makes arrow functions useful when passing one function as an argument to another (such as callbacks and lexical scopes).

This is an important and very useful feature of arrow functions.


// Traditional Callback
setTimeout(function () {
  console.log("Time's up!");
}, 5000);

// Arrow Function Callback
setTimeout(() => {
  console.log("Time's up!");
}, 5000);
Enter fullscreen mode Exit fullscreen mode

A very big difference between Arrow Functions and other types of Functions is the value of its this keyword and how it inherits it.

Take a look at this:

// Traditional Function with Context
const myObject1 = {
  value: 42,
  getValue: function () {
    return this.value;
  },
};

console.log(myObject1.getValue()); // Output: 42

// Arrow Function with Preserved Context
const myObject2 = {
  value: 42,
  getValue: function () {
    const value = 200;
    setTimeout(() => {
      console.log(this.value);
    }, 1000);
  },
};

myObject2.getValue(); // Output: 42 (after a delay of 1 second)

Enter fullscreen mode Exit fullscreen mode

I remember my job as a software intern. I called the this keyword on a password hashing and salting function and getting a widely different error than expected.

It took higher guidance to finally resolve it, that is what inspired my writing of this article after seeing a student of mine make the same mistake.

Don't get attached to any particular method

Arrow functions, function declarations, and function expressions — All come with their strengths and weaknesses. Real expertise is knowing how best to apply them with clarity and efficiency.

Arrow Functions may make your code flow smoothly, but you must be careful when using them in cases that require their own _"this" _contexts, such as object methods or constructors.

While Function Declarations can give the flexibility to be invoked directly as object methods, constructors, or assigned to variables, Function expressions on the other hand shine in areas where flexibility is important.

They can be assigned to variables or used as arguments in higher-order functions. Function expressions shine in callback scenarios, event handlers, and cases where dynamic function assignment is required.

What is software engineering if not the creative combination of the available tool functions in designing solutions to everyday problems?

Top comments (0)