Function Declaration vs. Function Expression
Today I want to write a little bit about functions in JavaScript, specifically the difference between a Function Declaration and a Function Expression.
Function Declaration
A declaration statement in JavaScript is one that defines either a variable or a function to an identifier, or a named space. These are statements that don't do much on their own, but simply describe either a value or functionality and save it to a named space in memory, making it available to be reused throughout your program.
When we write something like the following:
var person = "Joey";
we are writing a declaration statement to define a variable. When we write something like this:
function doSomething() { // logic goes here}
we are writing a declaration statement to define a function. This code demonstrates a Named Function Declaration, or otherwise simply a Function Declaration.
The structure of a Function Declaration is straightforward: First you use the function
keyword, then provide a name for the function (in this case, doSomething
), followed by a set of parenthesis ()
. These parentheses can contain parameter names that the function can expect to receive as arguments when the function gets called. Lastly, there is a set of curly brackets {}
, inside of which you will put the actual logic that you want the function to perform when it gets called.
Here is a more complete example of a Function Declaration:
function calculateArea(width = 5, height = 10) { return width * height;}
calculateArea();// expected result: 50calculateArea(100, 20);// expected result: 2000calculateArea(undefined, 100);// expected result: 500
In this example I have used a Function Declaration to define a function named calculateArea
. This function has two parameters: width
and height
. I have also given these parameters some default values of 5
and 10
, respectively. The function will use either (or both) of these default values if either (or both) of the arguments are undefined
when the function is called. Finally, within the curly brackets, the function simply returns the value of width
and height
multiplied.
Function Declarations are the way I go about defining most of the functions that I use in programming. The main benefit here is that the function gets assigned to a name, whether that's doSomething()
, calculateArea()
, or something else altogether. This enables the functionality to get reused throughout my program simply by calling the function name and passing it any necessary arguments.
Function Declaration Hoisting
Another thing to note about functions defined through Function Declaration is that they can be hoisted. I went into more detail about hoisting in this blog post. Observe the following code:
calculateArea();// expected result: 50
function calculateArea(width = 5, height = 10) { return width * height;}
In this example we have defined the same calculateArea()
function as above using a Function Declaration, but the key difference here is that we are calling the function before it is declared. Even though the function call comes before the declaration, it works as desired, because functions defined using Function Declaration get hoisted by the JavaScript engine.
Function Expression
With Function Declaration, we are able to create functions that can be called on and reused throughout our entire application. Sometimes you want that kind of broad, global availability. However, other times you might only want a function to be used in one place or context, but not be available anywhere else. This is where Function Expression, the other method for creating functions in JavaScript, comes in:
const subtotal = 100;const applyTax = function (subtotal) { return subtotal * 0.08;};const total = subtotal + applyTax(subtotal);console.log(total);// expected result: 108
In this little example, we've defined a variable, applyTax
, whose value is an anonymous function (more on that later) that accepts a subtotal
parameter, and returns the tax rate on that subtotal
. The functionality of multiplying subtotal
by .08
doesn't need to be reused in anything other than applying a tax rate to the subtotal, so it makes sense to use a Function Expression to assign this functionality to the variable applyTax
.
But before we go any further, we should understand what an Expression is, and how it relates to Function Expressions.
What is an Expression?
In JavaScript, an Expression is any valid piece of code that resolves to a value. Enumerating the various types of expressions in JavaScript is beyond the scope of this blog post, but here are some examples:
person = 'Joey'
is an expression that uses the assignment operator (=
) to assign a value to a variable. The assignment itself is a side effect of the expression statement, but the overall expression itself resolves to the value 'Joey'
.
7 + 10
is another type of expression statement. Even though on its own this statement won't do much, just add 7
and 10
together, it resolves to 17
but does not assign this result to a variable.
So again, an Expression is simply any statement in JavaScript that resolves to a value.
Where earlier we saw how to declare JavaScript functions, we can also write Expressions that resolve to a function. This is called a Function Expression.
In many cases, a function expression will be found on the right hand side of an assignment operation, whether that's an =
(i.e., var a = function(){...}
) or a :
within an object property declaration (i.e. var a = { b: function() {...} }
). Otherwise, a function expression can be used as a callback to another function or method (i.e., Array.map(function() {...})
), or as an Immediately Invoked Function Expression (i.e. (function() {...})()
).
The classic way of writing a function expression starts with the function
keyword. Then there can optionally be a name for the function between the function
keyword and the parentheses ()
. We then have the curly brackets {}
, inside of which the logic of the function gets defined.
For example:
var doSomething = function () { // logic goes here};
In this example, we have declared a variable, doSomething
, and assigned to it the value of a function. The function itself is not given a name, so it is called an anonymous function. In order to execute this anonymous function, we need to call the variable that we assigned it to:
var doSomething = function () { console.log("hi");};
doSomething();// expected result: "hi"
This is an example of an Anonymous Function Expression being assigned to a variable. Another method of creating an Anonymous Function Expression is to assign the function as a property on an object:
var someObject = { doSomething: function () { console.log("hi"); },};
someObject.doSomething();// expected result: "hi"
With Function Declarations, anonymous (unnamed) functions are not allowed, but with Function Expressions, anonymous functions are allowed.
However, we can also do Named Function Expressions:
var doSomething = function sayHi() { console.log("hi");};
doSomething();// expected result: "hi"
Alternatively:
var someObject = { doSomething: function sayHi() { console.log("hi"); },};
someObject.doSomething();// expected result: "hi"
Anonymous Function Expression vs. Named Function Expression
Why and when would you do an Anonymous Function Expression instead of a Named Function Expression (or vice versa)?
There are some subtle differences between these two approaches. Generally speaking, names are good things. Tracing an error to a named function is much easier than tracing one to an anonymous function. To account for this, certain JavaScript engine implementations are smart enough to try to infer the name of anonymous functions from context, but it's imperfect:
var someObject = { sayHi: function () { console.log("hi"); // "hi" console.log(typeof sayHi); // "undefined" console.log(someObject.sayHi.name); // "sayHi" }, sayHowdy: function sayHowdy() { console.log("howdy"); // "howdy" console.log(typeof sayHowdy); // "function" console.log(someObject.sayHowdy.name); // "sayHowdy" },};
someObject.sayHi();someObject.sayHowdy();
In the above example, the sayHi
property returns undefined
when we call typeof sayHi
. This is because this property evaluates to an anonymous function. When we do the same for the sayHowdy
property (a function expression that we named sayHowdy
), typeof sayHowdy
evaluates to "function"
. This distinction is dictated by the abstract operation SetFunctionName.
But to bring this back to something more practical, it's important to note that when you do a Named Function Expression, the name of that function is only available within the function itself:
var doSomething = function sayHi() { console.log(sayHi);};
doSomething();// expected result: function sayHi();
console.log(sayHi);// expected result: ReferenceError: sayHi is not defined
This behavior is intentional, as it allows us to make reliable recursive function calls, even when that function is written to another variable:
var countToTen = function countUp(counter) { console.log(counter); if (counter >= 10) { return; } else { countUp(++counter); }};countToTen(0);// expected result: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Recursive calls to Anonymous Function Expressions are simply not possible. Historically you could exploit arguments.callee
:
var countToTen = function (counter) { console.log(counter); if (counter >= 10) { return; } else { arguments.callee(++counter); }};countToTen(0);// expected result: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
But arguments.callee
is deprecated, and usage is disallowed in Strict Mode. In fact, right at the top of their arguments.callee
article, MDN gives a big warning:
Warning: The 5th edition of ECMAScript (ES5) forbids use of arguments.callee() in strict mode. Avoid using arguments.callee() by either giving function expressions a name or use a function declaration where a function must call itself.
So you would use a Named Function Expression for the following reasons:
- The name is necessary for recursive function calls
- Anonymous functions are not helpful in error tracing within the call stack
- Generally speaking, a function's name helps convey meaning and improves readability of the code
That being said, there are times when it's generally accepted to use Anonymous Function Expressions. The two that come to mind at the moment are:
(1) for callback functions:
[0, 1, 2, 3, 4, 5, 6].map(function (number) { return number * 2;});
(2) for Immediately Invoked Function Expressions (IIFEs):
(function () { console.log("Immediately invoked function expression");})();
I plan on writing a post soon about IIFEs, so I won't go into any detail about them here. However, I will note that in both of these cases a Named Function Expression would work just as well.
Arrow Functions
ES6 introduced Arrow Functions, which are a compact alternative to Anonymous Function Expressions.
Rather than writing:
var doSomething = function () { // logic goes here};
we can write:
var doSomething = () => { // logic goes here};
Or for a callback function:
[0, 1, 2, 3, 4, 5, 6].map((number) => { return number * 2;});
Or as an IIFE:
(() => { console.log("Immediately invoked function expression");})();
Arrow functions are always anonymous. They also have a number of limitations not found in traditional functions. Read more about Arrow Function Expressions on MDN.
Function Expression Hoisting
Now that we've taken a few detours through JavaScript Expressions, Anonymous vs. Named Function Expressions, and Arrow Functions, we can go back to comparing and contrasting Function Declarations and Function Expressions.
You'll recall that functions created through Function Declaration can be hoisted:
doSomething();// expected result: "hi"
function doSomething() { console.log("hi");}
Functions created through Function Expressions cannot be hoisted:
doSomething();// expected result: TypeError: doSomething is not a function
var doSomething = function sayHi() { console.log("hi");};
It's important to note that it's the function itself that is not hoisted, hence the 'doSomething is not a function'
TypeError that we get. The variable that we assign it to still follows the hoisting rules of var
/const
/let
variables. Therefore, if we change var
to const
or let
and try to hoist, then the actual variable itself will not be hoisted:
doSomething();// expected result: ReferenceError: doSomething is not defined
const doSomething = function sayHi() { console.log("hi");};
Function Expression and Object.defineProperty()
One last bit of behavior to note for Function Expressions is that their name can be rewritten using Object.defineProperty()
:
var doSomething = function sayHi() { console.log(doSomething.name); // expected result: "sayHi"
Object.defineProperty(doSomething, "name", { value: "sayHowdy", configurable: true, }); console.log(doSomething.name); // expected result: "sayHowdy"};
doSomething();
This is not possible for a Function Declaration.
Function Declaration vs. Function Expression
So let's summarize all of this.
Function Declarations require a name and allow you to define functionality that can be called on and used throughout your program.
Function Expressions can either be named or anonymous and allow you to define functionality that is attached to a variable or an object.
Named Function Expressions are generally preferable, as the name improves error tracing through the call stack, enables recursive function calls, and generally improves readability of code. Anonymous Function Expressions do have their place (though there is debate around this!), and are often found as callback functions and Immediately Invoked Function Expressions. Anonymous Function Expressions can be written either as Traditional Functions (i.e., using the function
keyword) or as Arrow Functions (i.e., using =>
). Arrow functions have a number of limitations to be aware of.
And here's a little summary of Function Declarations vs. Function Expressions across a number of different behaviors:
Behavior | Function Declaration | Function Expression |
---|---|---|
Named Function | Allowed | Allowed |
Anonymous Function | Disallowed | Allowed |
Hoisting | Allowed | Disallowed |
Renaming with Object.defineProperty | Disallowed | Allowed |
Sources / Further Reading
- Function Declaration on MDN
- Function Expression on MDN
- Expressions and Operators on MDN
- When to use a function declaration vs. a function expression by Amber Wilkie on freeCodeCamp
- You Don't Know JS by Kyle Simpson
- Why use named function expressions? Thread on StackOverflow
- arguments.callee on MDN
- Arrow function expressions