Functions
Functions are used to define reusable blocks of code that can be executed when called. Functions can accept parameters, perform operations, and return values.
Syntax
See Callable
Arguments: Passed by Value or Reference?
Arguments in this language are passed by value for primitive types (e.g., numbers and strings) and by reference for objects (arrays, functions, and objects).
Pass by Value
When a primitive type is passed as an argument, a copy of the value is made, and changes to the parameter inside the function do not affect the original argument.
fn increment(x) {
x = x + 1;
return x;
}
let num = 5;
increment(num); // num remains 5, as primitives are passed by value
Pass by Reference
When an object (like an array or an object) is passed as an argument, the function receives a reference to the original object. Therefore, any modifications inside the function affect the original object.
fn addElement(arr) {
arr.push(4); // Modifies the original array
}
let numbers = [1, 2, 3];
addElement(numbers); // numbers is now [1, 2, 3, 4]
Recursion
Recursion is a process in which a function calls itself directly or indirectly in order to solve a problem. Each recursive call works on a smaller or simpler portion of the problem, typically leading to a base case where the recursion stops.
fn recursiveFunction(parameters) {
if (baseCondition) {
return baseValue; // Base case to stop recursion
}
// Recursive case
return recursiveFunction(simplifiedParameters);
}
Notes on Recursion
Every recursive function needs a base case to terminate the recursion; without one, it would call itself indefinitely. Recursion is often more intuitive for problems with a naturally recursive structure, such as tree traversals or calculating factorials. However, be cautious with deeply nested recursion, as each call consumes stack space and can lead to a stack overflow.
Closures
A closure is a function that "remembers" its lexical scope, even when the function is executed outside that scope. This allows a function to retain access to variables from its outer function, even after the outer function has finished executing.
fn outer() {
let count = 0;
const increment = fn() {
count += 1;
return count;
}; //notice the semicolon
return increment;
}
let counter = outer(); // `counter` is now a closure
console.log(counter()); // 1
console.log(counter()); // 2
Practical Use of Closures
Closures are often used for data encapsulation, where internal data is kept private, and access to it is provided through specific methods.
fn createCounter() {
let count = 0;
return {
increment: fn() {
count += 1;
return count;
},
decrement: fn() {
count -= 1;
return count;
},
getCount: fn() {
return count;
}
};
}
let counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2