DEV Community

loading...

When to use the different variable and function declarations and what are the best practices?

Antonin Januska
I am who I am.
・6 min read

If you're a noob or a seasoned dev, you might look at code and look at recommendations for code style and wonder, why are these practices considered best practices?

Well, I hate to break it to you but majority of style preferences and best practices are more preferences and very subjectively "best". In fact, there are all kinds of "camps" in web development and JavaScript which occasionally clash on Twitter on something as mundane as the usage of a semicolon.

So, in this article, I'd like to tackle functions and variables to discuss their subjective "best practices" as well as reasons why not to use them.

Arrow Functions vs. Named Function

There are multiple ways of declaration a function. The arrow function lets you skip parenthesis in single-argument functions and the named function can be declared standalone or assigned to a variable. Here is a quick example:

function sum(leftNum, rightNum) {
  return leftNum + rightNum;
}

const sum = (leftNum, rightNum) => { 
  return leftNum + rightNum;
}

Enter fullscreen mode Exit fullscreen mode

Let's look at the feature differences of these two function declarations.

Hoisting

The first big one is hoisting. If you declare a named function outside of a scope block (like a do or another function or a class), it'll automatically be available everywhere at run time! This allows you to structure your JavaScript file with a clear order without having to think about if a function will be available. For example:

bootstrapApplication(); // this runs without an issue

function bootstrapApplication() {
  // logic here
} 
Enter fullscreen mode Exit fullscreen mode

But this doesn't work with arrow functions:

bootstrapApplication();

const bootstrapApplication = () => {
  // logic goes here
}

Enter fullscreen mode Exit fullscreen mode

In this example, the constant isn't available before it's declared.

If you check out my post on how I structure my javascript file, you'll see me taking advantage of it heavily and there are more examples.

The dreaded this

this refers to the object you're currently writing code in. Whether that's an actual JavaScript object or a class, or a function expression, this will let you access the prototype and it will let you access other internal variables.

Both named functions and function expressions create their own this within its scope which can be confusing because you don't always want to do that.

// using function expression
function counterApp(arr) {
  this.count = 0;

  arr.forEach(function (item) {
    this.count++; // which `this` is this?
  }); 

  return this.count;
}

// using named functions

function counterApp(arr) {
  this.count = 0;

  function namedFunction(item) {
    this.count++; // is `this` available
  }

  arr.forEach(namedFunction); 

  return this.count;
}
Enter fullscreen mode Exit fullscreen mode

In both cases, this actually refers to the this inside the function it's called. Eg. in the first example, this.count would be undefined, and same with the second example.

Arrow function's rise in popularity stems from this problem. Back in the day, you'd assign this to a variable: var self = this and run self.count++ in both examples. In fact, this is how Babel and other popular tools transpile arrow functions.

Here's what you can do using arrow functions:

// using arrow function
function counterApp(arr) {
  this.count = 0;

  arr.forEach(item => {
    this.count++; // which `this` is this?
  }); 

  return this.count;
}

// using arrow function assigned to a variable
function counterApp(arr) {
  this.count = 0;

  const namedFunction = (item) => {
    this.count++;
  };

  arr.forEach(namedFunction); 

  return this.count;
}
Enter fullscreen mode Exit fullscreen mode

In both of these example, this.count is what you'd expect it to be and refers to counterApp's internal count variable.

The shorthand

One advantage that arrow functions have over named functions is that it has very neat shortcuts in writing them:

// single argument functions can drop the parentheses
const doubleNumber = num => { 
  return num * 2;
};

// any arrow function can drop the curly braces and return 
// should the `return` be the only code in the function body
const multiplyNums = (num1, num2) => num1 * num2;

// this gets tricky if you're returning an object
// you can wrap the object in parentheses like so:
const initializeUser = userName => ({ name: userName, newUser: true });
Enter fullscreen mode Exit fullscreen mode

Why use one or the other?

I've yet to meet a developer that will tell you to use one or the other in all cases. Instead, there are some best practices you can follow and I'll share those, just keep in mind that this is super subjective and most are personal preferences.

When to use arrow functions

// closures/callbacks
const userIds = users.map(user => user.id);

// when you need to access `this`
function application() {
  this.count = 0;

  const button = document.querySelector('.increment-button');

  button.addEventListener('click', () => this.count++);
}
Enter fullscreen mode Exit fullscreen mode

When to use named functions

// when hoisting is important
function applicationBootstrap(users) {
  const userIds = users.map(getProperty('id'));
}

// gets hoisted and is available for `applicationBootstrap`
function getProperty(prop) {
  // oooh, mixing things up!
  return item => item[prop];
}
Enter fullscreen mode Exit fullscreen mode

What about everywhere else?

It's all up to you! Seriously. Arrow functions or named functions in module exports? Your call.

Let vs Const vs Var

Oh no, oh no, oh no. I'm not touching this one...

Okay, I am. Especially since I used const a bunch of times already.

NOTE Please assume I'm running everything in strict mode.

Scope

A long time ago, we started with var. var does exactly just what it says: it declares a variable! That variable exists within a scope. A scope is usually a function (any type of function!), a module, or an object. Eg:

var hello = 'hi';

function welcome() {
  var worldName = 'world';
  console.log(hello, worldName); // accessible
}

console.log(worldName); // not accessible and undefined
Enter fullscreen mode Exit fullscreen mode

Cool, keep this in mind. You can declare a variable within for loops and while loops and the variable will exist outside of them:

for (var i = 0; i < 10; i++) {
  var finalCount = i;
}

console.log(finalCount); // accessible
console.log(i); // accessible as well!
Enter fullscreen mode Exit fullscreen mode

Both let and const follow the scope rules with objects and functions but do not follow the rules of the for and while loops. So if you write an identical for loop:

let initialCount = 0;

for (let i = 0; i < 10; i++) {
  let  finalCount = i;
}

console.log(initialCount); // only one accessible
console.log(finalCount); // inaccessible
console.log(i); // inaccessible as well!
Enter fullscreen mode Exit fullscreen mode

Variable assignment

Both var and let let you reassign a variable after initialization:

var initialized = false;

let userRole = 'read only';

if (userId === 1) {
  userRole = 'admin';
}

initialized = true;
Enter fullscreen mode Exit fullscreen mode

Both var and let are interchangeable in this example; however, const is meant to be a constant meaning that a variable cannot be reassigned once declared:

fucntion preciseNumber(num) {
  const precision = 8;

  if (num > 10) {
    precision = 4; // this will throw an error
  }

  return num.toFixed(precision);
}
Enter fullscreen mode Exit fullscreen mode

BUT, notice I say reassignment, not mutation. What's the difference? If you assign an object or an array to a const, you can manipulate it.

const user = {
  id: 1,
  name: 'Antonin Januska',
  active: false,
  monthsSinceLastActive: 3,
}

const inactiveUsers = [user];

if (user.monthsSinceLastActive < 10) {
  user.active = true; // this works!
  inactiveUsers.pop(); // works, too
}

user = getNextUser(); // fails because we're assigning a new object
inactiveUsers = [user]; // this would fail, too
Enter fullscreen mode Exit fullscreen mode

When to use which?

This is a source of contention. Everyone has opinions.

var or let?

I typically don't see a reason to ever use var and many developers would agree. Even in situations where var would be useful (like getting the last value of a for loop), it's more explicit to write the workaround. Again, these are my personal opinions:

let lastValueLet = 0;

for (let i = 0; i < 10; i++) {
  // I keep expecting this to error out
  // because of redeclaration
  var lastValueVar = i;
  lastValueLet = i;
}

// I'd get tripped up by this, does it exist?
// what would be the value?
console.log(lastValueVar); 

// very clear that it exists
console.log(lastValueLet); 
Enter fullscreen mode Exit fullscreen mode

Verdict: I default to let whenever possible.

const?

Constants are even more controversial. One of the reasons is because of the object gotcha where you can change the object despite being a constant. Remember, constants prevent reassignment, not mutation!

There are two camps/ideologies which I've seen regarding this:

True constants

Constant should be used only for numbers, strings, etc. but not objects. They should be used for declaring some kind of configuration or a mathematical constant (such as short-hand PI):

const PI = 3.14; 

function calculateArea(radius) {
  return radius * PI * PI;
}
Enter fullscreen mode Exit fullscreen mode

This camp usually recommends uppercasing constants which is something you'd do in other languages as well:

const MAX_HEALTH_VALUE = 100;

function drinkHealthPotion(currentHP, restoreAmount) {
  if (currentHP + restoreAmount > MAX_HEALTH_VALUE) {
    return MAX_HEALTH_VALUE;
  }

  return currentHP + restoreAmount;
}
Enter fullscreen mode Exit fullscreen mode
"Don't touch" constants

With full knowledge of the constant caveats, the second camp is more concerned about the intent and recommend using const to signify that you do not intent to change that variable. This is often used when destructuring arguments provided to a function or when declaration a function expression:

const sumFunctionExpression = (num1, num2) => num1 + num2;

function getUserId(user) {
  const { id } = user;

  return id;
}
Enter fullscreen mode Exit fullscreen mode

Essentially, using const is for anything and everything you don't want other developers to change in the future when they touch your code. And as an indicator to yourself that you don't want to change that variable.

This is my personal preferences.

What about you? What's your personal convention regarding function and variable declaration?

Discussion (1)

Collapse
jefferyhus profile image
El Housseine Jaafari

Good article and well organized. As of var & let I tend to use var when I want a variable to be visible in the whole application or scope and leave let for loops or variables inside functions. And for functions I only tend to use arrow/fat function whenever I want to use the global/local this.