For those familiar with JavaScript, you’ll know there are two main methods of declaring functions. The first is the traditional function f() {}
declaration, which looks as follows:
function someFunc(x, y) {
return x + y;
}
someFunc(1 , 3); // 4
The second is the lambda declaration or “const function” which involves assigning an anonymous function to a variable, typically using the “arrow” () => {}
syntax:
const someFunc = (x, y) => {
return x + y;
}
someFunc(1 , 3); // 4
One might assume that although they differ in syntax, they achieve the same thing, and so you may stick to one, or use a mixture of both in your JS development. That, or each developer working on a codebase has their preference and will stick to it without establishing or following a standard.
While this approach may be harmless in most cases, there are a few functional (geddit?) differences between the syntaxes which could make or break your codebase.
Hoisting && Scope Link to heading
A key feature of traditional JS functions is that their definitions are hoisted to the top of the scope they are defined in. For example, a traditional function can be called even above it’s declaration so long as it is in the same scope:
someHelloFunc(); // Prints "Hello Sean" - In the same scope
someOtherHelloFunc(); // Fails - Function is in a different scope
// Gets hoisted to top of scope
function someHelloFunc() {
console.log('Hello Sean');
}
if (true) {
// Gets hoisted to top of this "if" scope
function someOtherHelloFunc() {
console.log('Goodbye Dave');
}
}
By contrast, const functions do not get hoisted, and they too remain in their scope, like a regular const variable would. This means they are defined line by line and cannot be invoked unless they have already been declared:
const someHelloFunc = () => {
console.log('Hello Sean');
}
someHelloFunc(); // Prints "Hello Sean" - Function has been declared
someOtherHelloFunc(); // Fails - Function has not been declared
const someOtherHelloFunc = () => {
console.log('Goodbye Dave');
}
Mutability Link to heading
One key advantage of const expressions in particular is they immutable (assuming you use the const
keyword and not let
/var
). This means said functions are protected from being modified:
function traditional() {
console.log('I am traditional');
}
const constant = () => {
console.log('I am constant');
}
// Modify the traditional function
traditional = 'Ha! Your function is a string now!!!';
// Modify the constant function
constant = 'Become a string!!!'; // Will fail - constant is const and cannot be reassigned
traditional(); // This would fail since traditional is not a function at this point
constant();
I trust you would not modify a function in this way intentionally :)
Readability Link to heading
It’s up for debate which of the two is more readable. On one hand, traditional functions indicate a clear intent that a function is being declared, whereas const
initially may give the impression that it is a variable, until you see the () => {}
proving it’s a function.
We can also take advantage of the hoisting feature of traditional functions to put important functions near the top of a file, front and centre. Consider this mini module:
export default function someRoutine(number) {
return partOne(number)
}
function partOne(number) {
number++;
return partTwo(number);
}
function partTwo(number) {
number *= 2;
return number;
}
Here, our default function is front and centre as the other functions in the file are steps of a routine, thus may be less important unless you intend on modifying the file. The module is also clearly laid out with all the functions in order, step-by-step.
But let’s assume another developer modifies the module, adding a third step, and this is the new file:
export default function someRoutine(number) {
return partOne(number)
}
function partThree(number) {
number *= number;
return number;
}
function partOne(number) {
number++;
return partTwo(number);
}
function partTwo(number) {
number *= 2;
return partThree(number);
}
All of a sudden, order is lost and we have dreaded spaghetti code!!! Now imagine this on a large module edited time and time again and you can begin to imagine the headache that would be caused reading the file.
On the other hand, if instead we were to use const functions, the function order would need to be reversed. But since code is read top to bottom, line by line, it’s easy to know to look towards the bottom of the file for the important functions:
const partThree = (number) => {
number *= number;
return number;
}
const partTwo = (number) => {
number *= 2;
return partThree(number);
}
const partOne = (number) => {
number++;
return partTwo(number);
}
const someRoutine = (number) => {
return partOne(number)
}
export default someRoutine;
Perhaps more importantly, the const functions in this example enforce readability and structure, making the module easier to maintain and add new functions.
Which one should I use? Link to heading
While it is clear my preference is const functions, as long as you understand the differences between the two, it doesn’t matter which you use.
It’s worth noting that lambda functions have limitations of their own such as having no bindings to super()
or lacking functionality to be used as generator functions and use the yield
keyword, thus traditional functions should be used in these cases (See here for more information).
Perhaps the best approach is to take advantage of the features of both and use them situationally as outlined in this post, where the gist is use function
in global scope and in specific cases like the aforementioned need to use the yield
keyword, and use () => {}
everywhere else.
Now off with ye! Go code something! Happy Hacking!