JavaScript Unwrapped (Part IV): Basics of Functions & Function Context
Topics: Basics of Functions & Function Context
Mastering the basics of Functions in JavaScript
In our previous article, we delved into key JavaScript concepts such as let
, const
, and var
, explored operators, and examined the powerful switch
statement, if-else
statements, and the intricacies of hoisting. These fundamental topics lay the groundwork for understanding how JavaScript operates under the hood and help you write cleaner, more efficient code.
Now that you have a solid grasp of these essentials, it's time to shift our focus to one of the most critical components of JavaScript: functions. Functions are at the heart of JavaScript programming—they allow you to encapsulate code into reusable blocks, making your code more modular and easier to manage. In this article, we'll explore different types of functions, including function declarations, expressions, arrow functions, default parameters, and higher-order functions. Let's dive in!
1. Function Declarations: The Classic Approach
Function declarations are the traditional way to define functions in JavaScript. They are defined using the function
keyword and are hoisted to the top of their scope, allowing you to call them before their definition in the code.
Syntax:
Example:
Explanation:
Declaration:
function greet(name) { ... }
defines a function namedgreet
that takes one parameter,name
.Hoisting: The
greet
function can be called before its declaration due to hoisting.
Function declarations are straightforward and easy to understand, making them a reliable choice for defining functions in JavaScript.
2. Function Expressions: Anonymous Functions
Function expressions involve defining a function and assigning it to a variable.
Unlike function declarations, function expressions are not hoisted.
They must be defined before you can use them.
Syntax:
Example:
Explanation:
Expression:
const greeting = function(name) { ... };
creates a function and assigns it to thegreeting
variable.Anonymous: This function doesn’t have a name and is often used when a function is needed as a value, such as in callbacks.
Function expressions are useful for cases where you need a function only in a specific context and don’t need to refer to it by name.
3. Arrow Functions: Concise and Modern
Arrow functions provide a more concise syntax for writing functions. They also have a different behavior for this
, making them suitable for certain use cases, especially in modern JavaScript.
Syntax:
Example:
Shorter Syntax:
For functions that have a single expression, you can omit curly braces and the return
statement:
Concise: Arrow functions allow you to write functions more compactly.
No this
Binding: Arrow functions inherit this
from their surrounding context, which can be useful in some scenarios. (don’t worry if this goes above your head! - JUST LOOK AT THE NEXT SECTION!)
Arrow functions are a modern addition to JavaScript that can make your code more readable and succinct.
Function Context (Important!)
Function Declarations and Function Expressions
In regular functions (both declarations and expressions), this
refers to the object that the function is called on (execution context). If the function is called in the global context, this
refers to the global object (window
in browsers or global
in Node.js). Note that, all functions that we wrote in this article are called in the global context.
However, if a function is called as a method of an object, this
refers to that object.
(don’t panic if you don’t understand the last sentence! - this will be cleared when we do objects. Focus on the bold and italicized sentence!)
Let us look at an example. This example will also make it clear why it is recommended to run JavasScript code in strict mode using the words ‘use strict
’ at the beginning of the script.
In Non-Strict Mode: this
defaults to the global object.
In Strict Mode: this
is undefined
in regular functions when they're invoked in the global context.
Arrow Functions
Arrow functions are different from regular functions in how they treat this
. In arrow functions, this
is lexically bound, meaning it uses this
from the surrounding code at the time the function is created.
It does not refer to the object on which it was called but instead takes the this
value from its enclosing function or scope.
const arrowFunc = () => {
console.log(this);
};
arrowFunc();
// `this` refers to the surrounding context (global object in this context), not to the global object always
Key Differences
Regular functions:
this
depends on how the function is called. It can refer to the global object, an object that the function is called on, or beundefined
in strict mode.Arrow functions:
this
is always inherited from the surrounding scope at the time the function is defined, making it more predictable.
Let us write the same code in strict mode and understand the differences it makes!
Explanation of the Output in Strict Mode
regularFunction()
Call:In strict mode, when
regularFunction
is called,this
isundefined
because the function is invoked in the global context without any object as its owner.Output:
"Regular Function:" undefined
arrowFunction()
Call:Arrow functions do not have their own
this
. Instead,this
is lexically bound to the scope in which the arrow function was defined. Here,arrowFunction
was defined in the global context, sothis
refers to the global object (even in strict mode).Output:
"Arrow Function:" [object Window]
(or[object global]
in Node.js)
outerFunction()
Call:Inside
outerFunction
, we have two nested functions:innerFunction
(a regular function) andinnerArrowFunction
(an arrow function).
a.
innerFunction()
Call:In strict mode,
this
insideinnerFunction
isundefined
because it's a regular function not invoked as a method of an object.Output:
"Inside Regular Function:" undefined
b.
innerArrowFunction()
Call:Since
innerArrowFunction
is an arrow function, it inheritsthis
from theouterFunction
's context. In strict mode,outerFunction
itself is executed withthis
asundefined
, soinnerArrowFunction
logsundefined
.Output:
"Inside Arrow Function:" undefined
Summary of Outputs in Strict Mode
// Regular Function: undefined
// Arrow Function: [object Window]
// Inside Regular Function: undefined
// Inside Arrow Function: undefined
Conclusion
Regular Functions: In strict mode,
this
isundefined
when the function is invoked without an explicit context (like the global object).Arrow Functions: They inherit
this
from their defining scope. If defined in a scope wherethis
isundefined
(as it is in strict mode),this
remainsundefined
inside the arrow function as well.
What happens when function is defined within an object? (IMPORTANT!)
(NOTE: I am adding this section, so that, you can refer to this article for a complete reference on Function Context. Come back to this section, once you are done with objects in JS)
When a function is defined inside an object, the behavior of this
changes based on whether the function is a regular function or an arrow function. Let's explore how this
behaves in both cases, with and without strict mode.
Regular Function Inside an Object
For regular functions, this
typically refers to the object that owns the method, regardless of strict mode.
Output: "Regular Function:" { name: "JavaScript", regularFunction: ƒ }
Explanation:
this
refers toobj
becauseregularFunction
is called as a method ofobj
.In both strict mode and non-strict mode,
this
points toobj
when invoked asobj.regularFunction()
.
Arrow Function Inside an Object
For arrow functions, this
is lexically bound, meaning it doesn't refer to the object the function is a method of. Instead, it refers to this
from the outer scope where the arrow function was defined.
Understanding this
in Arrow Functions
When you use an arrow function inside an object, the this
keyword is not bound to the object. Instead, this
is lexically bound, meaning it takes the value of this
from the surrounding context where the arrow function was defined.
Explanation of the Output
Arrow Function Lexical Binding:
The arrow function in
arrowFunction
does not have its ownthis
. Instead, it inheritsthis
from the lexical scope in which it was created.Since the arrow function was created in the global scope (where
obj
is defined),this
refers to the global object.
Strict Mode Behavior:
In strict mode, the global
this
isundefined
if accessed in a regular function. However, in the global context,this
still refers towindow
(orglobal
in Node.js) when accessed directly.The arrow function inherits
this
from where it was defined (the global context), which iswindow
, even in strict mode.
Output
When you run the code, this
inside the arrow function refers to the global object (window
in browsers or global
in Node.js):
Arrow Function: Window {...}
Summary
Arrow Function in an Object:
The arrow function does not create its own
this
context. Instead, it capturesthis
from the surrounding (lexical) context, which, in this case, is the global scope.Even in strict mode, since the arrow function was defined in the global scope,
this
points to the global object (window
).
Despite strict mode being enabled, the arrow function still logs the global object because this
is lexically bound to where the function is defined, not where it is called.
Let us now look at Nested Objects
When an arrow function is defined within an object, it does not have its own this
context. Instead, it lexically inherits this
from the scope in which it was defined. If an arrow function is defined directly inside an object but not inside a method of that object, this
refers to the outer (lexical) context.
In a typical global scope (such as in a browser), the outer context is the global object (window
). Even in strict mode, the arrow function captures this
from where the object was created, which is still the global object. Thus, this
will refer to window
inside the arrow function.
Explanation
Arrow Function: The arrow function is defined inside
nestedObj
. Arrow functions do not create their ownthis
context; instead, they inheritthis
from the surrounding lexical environment.Global Scope: In this example, the surrounding lexical environment is the global scope because the arrow function is not enclosed by another function. As a result,
this
refers to thewindow
object in a browser.Strict Mode: Even in strict mode, the arrow function’s
this
still refers to thewindow
object because the global context is where the object was created, and the arrow function does not redefinethis
.Output
// Inside arrowFunction: Window { ... }
Note this statement in this example: The surrounding lexical environment is the global scope because the arrow function is not enclosed by another function.
The above statement raises the question: What happens if the arrow function is enclosed by another function?
What happens if the arrow function is enclosed by another function?
Explanation
regularFunction
Method:The
regularFunction
is a method ofobj
. When you callobj.regularFunction()
,this
insideregularFunction
refers toobj
.Output:
"Inside regularFunction:" { name: "JavaScript", regularFunction: ƒ }
Arrow Function Inside
regularFunction
:The arrow function
arrowFunction
is defined insideregularFunction
. Since arrow functions don’t have their ownthis
, they inheritthis
from the lexical context in which they were defined.In this case,
this
insidearrowFunction
will be the same asthis
insideregularFunction
, which isobj
.Output:
"Inside arrowFunction:" { name: "JavaScript", regularFunction: ƒ }
Summary of Output
// Inside regularFunction: { name: "JavaScript", regularFunction: ƒ }
// Inside arrowFunction: { name: "JavaScript", regularFunction: ƒ }
Key Points
Regular Function (
regularFunction
):this
insideregularFunction
refers to the objectobj
because it is called as a method ofobj
.Arrow Function (
arrowFunction
): The arrow function insideregularFunction
inheritsthis
fromregularFunction
. Sincethis
inregularFunction
refers toobj
,this
insidearrowFunction
also refers toobj
.
When an arrow function is defined inside a regular function within an object, this
inside the arrow function is lexically bound to the this
of the outer function (in this case, regularFunction
). If the outer function is a method of an object, this
inside both the regular function and the arrow function will refer to that object.
OK! Now, I expect that you have a clear understanding of the function context of regular and arrow functions.
Let us solve an exercise!
Consider the following code:
If you have chosen the first option, consider going through the section again. If you have chosen the second option, and you are a complete beginner, GREAT! Tally your answer against the one I’ll provide. Last but not the least, if you are last option guy, CONGRATS! WELL DONE!
Let’s now see the explanation and the result we get.
Explanation
outerObj
andnestedObj
:outerObj
contains a propertyouterName
and a nested objectnestedObj
.nestedObj
contains a propertynestedName
and a methodregularFunction
.
regularFunction
Method:The
regularFunction
is a method ofnestedObj
. When you callouterObj.nestedObj.regularFunction()
,this
insideregularFunction
refers tonestedObj
.Output:
"Inside regularFunction:" { nestedName: "Nested Object", regularFunction: ƒ }
Arrow Function Inside
regularFunction
:The arrow function
arrowFunction
is defined insideregularFunction
. Since arrow functions do not have their ownthis
, they inheritthis
from the lexical context in which they were defined.Here, the arrow function inherits
this
fromregularFunction
, which isnestedObj
.Output:
"Inside arrowFunction:" { nestedName: "Nested Object", regularFunction: ƒ }
Summary of Output
// Inside regularFunction: { nestedName: "Nested Object", regularFunction: ƒ } // Inside arrowFunction: { nestedName: "Nested Object", regularFunction: ƒ }
Key Points
Regular Function (
regularFunction
):this
insideregularFunction
refers tonestedObj
because it is called as a method ofnestedObj
.Arrow Function (
arrowFunction
): The arrow function insideregularFunction
inheritsthis
fromregularFunction
. Sincethis
inregularFunction
refers tonestedObj
,this
insidearrowFunction
also refers tonestedObj
.
When an arrow function is nested inside a regular function that is a method of a nested object, the this
value inside the arrow function is lexically bound to the this
value of the regular function. Since the regular function is a method of the nested object, this
refers to that nested object, and so does this
inside the arrow function.
(WE ARE DONE WITH OBJECTS! YOU CAN CARRY ON FROM HERE)
Default Parameters: Handling Missing Arguments
Default parameters allow you to specify default values for function parameters. This is handy when you want to ensure that a function has a fallback value if no argument is provided.
Syntax:
Example:
Explanation:
Defaults:
name = 'Guest'
ensures thatname
defaults to'Guest'
if no argument is provided.Flexibility: Allows functions to handle optional parameters gracefully.
Default parameters simplify function definitions and avoid the need for manual checks for undefined values.
Higher-Order Functions: Functions That Handle Functions
Higher-order functions are functions that either take other functions as arguments or return functions. They are a key concept in functional programming and can lead to more flexible and reusable code.
Syntax:
Example:
Explanation:
Function as Argument:
repeat
takes a functionaction
and a numbertimes
, and executesaction
multiple times.Flexibility: Higher-order functions can create powerful abstractions and reusable patterns.
Higher-order functions enable advanced coding techniques and can simplify complex operations by encapsulating behavior.
Conclusion
Understanding functions in JavaScript—from declarations and expressions to arrow functions, default parameters, and higher-order functions — is essential for writing effective code. Each type of function serves a specific purpose and can be used in different scenarios to improve your coding practices.
In our next article, we'll dive into arrays and objects, helping you deepen your understanding and enhance your JavaScript skills. Stay tuned for more insights and tips to further elevate your coding expertise!
Happy coding!