Skip to content

ES6 - Functions

Functions are fundamental building blocks in JavaScript, allowing you to encapsulate a block of code that can be executed multiple times. ES6 (ECMAScript 2015) and subsequent versions introduced several powerful enhancements to function syntax and capabilities.

Hoisted to the top of their scope, meaning they can be called before they are defined in the code.

function greet(name) {
console.log(`Hello, ${name}!`);
}
greet("World"); // Output: Hello, World!

Assigned to a variable. Not hoisted; can only be called after the assignment.

const add = function(a, b) {
return a + b;
};
console.log(add(5, 3)); // Output: 8

Arrow functions provide a more concise syntax for writing function expressions and have a significant difference in how this is handled: they lexically bind this, meaning this inside an arrow function refers to the this value of its surrounding (enclosing) lexical scope. They are not suitable for all situations (e.g., object methods that need their own this, or as constructors).

  • Basic: (param1, param2) => { statements }
  • Single parameter (parentheses optional): param => { statements }
  • No parameters: () => { statements }
  • Single expression (implicit return, braces and return keyword omitted): (param1, param2) => expression
const multiply = (x, y) => x * y;
console.log(multiply(4, 5)); // Output: 20
const square = num => num * num;
console.log(square(7)); // Output: 49
const sayHi = () => console.log("Hi there!");
sayHi(); // Output: Hi there!
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // Output: [2, 4, 6, 8]
function Timer() {
this.seconds = 0;
// In traditional function, 'this' inside setInterval would be global or undefined (strict mode)
// setInterval(function() {
// this.seconds++; // 'this' is not Timer instance here
// console.log(this.seconds);
// }, 1000);
// Arrow function inherits 'this' from Timer constructor scope
setInterval(() => {
this.seconds++;
console.log(`Timer (lexical this): ${this.seconds}`);
}, 1000);
}
// const myTimer = new Timer(); // Uncomment to run this example in a browser/Node environment

You can specify default values for function parameters. If an argument for that parameter is not provided or is undefined, the default value is used.

function greetUser(name = "Guest", message = "Welcome") {
console.log(`${message}, ${name}!`);
}
greetUser(); // Output: Welcome, Guest!
greetUser("Alice"); // Output: Welcome, Alice!
greetUser("Bob", "Good morning"); // Output: Good morning, Bob!

The rest parameter syntax (...paramName) allows a function to accept an indefinite number of arguments as an array. It must be the last parameter in the function definition.

function sumAll(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sumAll(1, 2, 3)); // Output: 6
console.log(sumAll(10, 20, 30, 40, 50)); // Output: 150
console.log(sumAll()); // Output: 0

Spread Syntax (...) with Function Calls (ES6)

Section titled “Spread Syntax (...) with Function Calls (ES6)”

The spread syntax allows an iterable (like an array) to be expanded into individual arguments in a function call.

function calculateProduct(a, b, c) {
return a * b * c;
}
const values = [2, 3, 4];
console.log(calculateProduct(...values)); // Equivalent to calculateProduct(2, 3, 4) -> Output: 24

Functions can also be created dynamically using the Function constructor: new Function(arg1, arg2, ..., functionBody). However, this is generally not recommended due to security risks (similar to eval()) and performance implications, as the function body is parsed at runtime.

A function that calls itself is recursive. Recursion is useful for solving problems that can be broken down into smaller, self-similar subproblems. A base case is needed to stop the recursion.

function factorial(n) {
if (n < 0) return undefined; // Factorial not defined for negative numbers
if (n === 0 || n === 1) {
return 1; // Base case
} else {
return n * factorial(n - 1); // Recursive step
}
}
console.log(factorial(5)); // Output: 120

Immediately Invoked Function Expressions (IIFE)

Section titled “Immediately Invoked Function Expressions (IIFE)”

An IIFE is a function that is defined and executed immediately. Historically, IIFEs were used to create a private scope for variables to avoid polluting the global scope (using var). With ES6 let and const providing block scope, and modules providing true encapsulation, the need for IIFEs for scope management has diminished, but they can still be useful for initialization or to create closures.

(function() {
const message = "This is from an IIFE!";
console.log(message);
})();
// message is not accessible here

Generator functions (function* name() { ... }) are a special type of function that can be paused and resumed, allowing them to produce a sequence of values over time. They use the yield keyword to pause and return a value. When called, a generator function returns an iterator object.

function* countUpTo(max) {
let count = 1;
while (count <= max) {
yield count++; // Pause and yield the current count
}
}
const counter = countUpTo(3);
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }
// Can also be used with for...of loop
for (const num of countUpTo(3)) {
console.log(`For...of: ${num}`);
}

Generators are foundational for advanced asynchronous patterns like those used in async/await (though async/await itself is a higher-level abstraction over promises).