W04 Learning Activity: Advanced Function Concepts
Overview
The JavaScript engine offers powerful ways to solve problems by using functions to organize code and perform specific tasks. You already know how to define and call functions, pass arguments to them, and handle their return values. In this activity, you'll explore how JavaScript functions are executed and how they inter act with the call stack, and how they can be used as first-class objects.
Prepare
This learning activity focuses on a deeper understanding of functions including the execution context, the callstack, callbacks, and higher order functions.
What is a first-class object?
In JavaScript, functions are first-class objects. This means that functions can be treated like any other object in JavaScript. They can be assigned to variables, passed as arguments to other functions, returned from functions, and stored in data structures like arrays and objects. This allows for a high degree of flexibility and reusability in your code.
As you know, functions can be created using function declarations, function expressions, and arrow functions. Each of these methods has its own syntax and use cases, but they all create first-class functions that can be used in the same way.
Here is a simple example the demonstrates passing a function as an argument:
const fullNames = ["Aday Lovina", "Benstras Turing", "Grace Ojo"];
const lastNames = fullNames.map(function getLastName(name) {
return name.split(" ").slice(-1)[0];
});
map()
is a higher-order function. It returns a new array.getLastName()
is the function passed as an argument tomap
name
is the parameter of the functiongetLastName()
name.split(" ")
splits the string into an array of wordsslice(-1)[0]
gets the last element of the arrayreturn
returns the last namelastNames
is the array of last names returned by themap()
function
Higher Order Functions are functions that can take other functions as arguments or return functions as their result. This allows for more flexible and reusable code, as well as the ability to create more complex functionality by combining simple functions. The are a fundamental and major part of functional programming and include functions that you have already been using including:
map()
– creates a new array with the results of calling a provided function on every element in the calling array.filter()
– creates a new array with all elements that pass the test implemented by the provided function.reduce()
– executes a reducer function (that you provide) on each element of the array, resulting in a single output value.forEach()
– executes a provided function once for each array element.
Execution Context
The Execution Context is the environment in which a function is executed. It includes the scope of the function, the value of 'this', and any variables that are defined within the function. Understanding the execution context is crucial for understanding how functions work in JavaScript.
The following example demonstrates how contexts are created and how scope chains determine what variable can be accessed at different levels. You should run this code in the console to verify the concept.
const globalVariable = "I'm Global";
function outerFunction() {
const outerVariable = "I'm Outer";
function innerFunction() {
const innerVariable = "I'm Inner";
console.log(globalVariable); // I'm Global
console.log(outerVariable); // I'm Outer
console.log(innerVariable); // I'm Inner
}
innerFunction();
console.log(innerVariable); // ReferenceError: innerVariable is not defined
console.log(this); // in this scope, refers to the global object (the window in browsers)
}
outerFunction();
In this example, the innerFunction()
has access to the variables defined in its own scope
(innerVariable
) as well as the variables defined in its parent scope (outerVariable
)
and the global scope (globalVariable
). However, the outerFunction()
does not have
access to the innerVariable
, because it is defined in a different scope.
In the example, the this
keyword refers to the global object (the window in browsers) because
outerFunction()
is called in the global context. If you were to call innerFunction()
directly, this
would refer to the function itself.
Hoisting
Hoisting is a JavaScript mechanism where variables and function declarations (but not arrow functions) are moved to the top
of their containing scope during the compile or "Creation" phase. It also sets up the this
binding.
Hoisting is a mechanism that affects what is available and when, before any code is actually run.
Call Stacks
Call Stacks in JavaScript is a data structure that keeps track of function calls. When a function is called, it is added to the stack, and when it returns, it is removed from the stack. This allows JavaScript to keep track of where it is in the code and what functions are currently being executed.
Here is an example that you can run in the console:
function firstFunction() {
console.log("First Function");
secondFunction();
console.log("Back to First Function");
}
function secondFunction() {
console.log("Second Function");
thirdFunction();
console.log("Back to Second Function");
}
function thirdFunction() {
console.log("Third Function");
// Uncomment the next line to see the stack overflow error (RangeError)
// thirdFunction();
console.log("Back to Third Function");
}
firstFunction();
In this example, when firstFunction()
is called, it adds itself to the call stack. Then it calls
secondFunction()
, which adds itself to the stack. Finally, it calls thirdFunction()
,
which adds itself to the stack. When thirdFunction()
returns, it is removed from the stack, and
control goes back to secondFunction()
, and then back to firstFunction()
.
Optional Resources
- First-Class Function – MDN Glossary
- The Function Object – MDN
- Call Stack – MDN Glossary
- Execution Context – MDN Glossary
- Higher-Order Function – MDN Glossary