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 it, the function would call itself infinitely.
- Recursion can be more intuitive for solving problems that have an inherently recursive structure, such as tree traversals or factorial calculations.
- Be mindful of stack overflow when using recursion with deep levels, as each recursive call uses up stack space.
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