JavaScript Functions Advanced
In JavaScript, functions are first-class citizens. Think of them like any other value: you can store a function in a variable, pass it to another function, or return it from one. They're as flexible as numbers or strings.
const greet = function() { return "Hello!"; };
console.log(greet()); // Hello!
A higher-order function either takes a function as an argument, returns a function, or both. You'll run into them everywhere in JavaScript, especially with array methods like map, filter, and reduce.
function applyTwice(fn, value) {
return fn(fn(value));
}
const double = x => x * 2;
console.log(applyTwice(double, 3)); // 12
A callback function is a function you hand off to another function, saying "run this when you're ready." You'll use callbacks a lot with API calls, timers, and event listeners.
function greetUser(name, callback) {
const message = "Hello, " + name + "!";
callback(message);
}
greetUser("Alice", msg => console.log(msg)); // Hello, Alice!
setTimeout schedules a function to run once after a given delay in milliseconds. It returns a timer ID that you can pass to clearTimeout to cancel the scheduled call before it runs.
const timer = setTimeout(() => {
console.log("Runs after 2 seconds");
}, 2000);
// Cancel before it runs
clearTimeout(timer);
setInterval works like a repeating alarm. It runs your function over and over at a fixed interval (in milliseconds). When you want it to stop, pass the interval ID to clearInterval.
let count = 0;
const interval = setInterval(() => {
count++;
console.log("Count:", count);
if (count === 3) clearInterval(interval);
}, 1000);
call() and apply() let you invoke a function with a specific this value. The difference is how you pass arguments: call() takes them one by one, while apply() takes them as an array.
function introduce(city, country) {
console.log(this.name + " from " + city + ", " + country);
}
const person = { name: "Alice" };
introduce.call(person, "Paris", "France");
introduce.apply(person, ["Paris", "France"]);
bind() creates a new function with a fixed this value. Unlike call() and apply(), it does not call the function right away. You can store the bound function and call it later.
function greet() {
console.log("Hi, " + this.name);
}
const user = { name: "Bob" };
const boundGreet = greet.bind(user);
boundGreet(); // Hi, Bob
An IIFE runs the moment you define it. Think of it like a one-time setup: it creates its own private scope, so nothing inside leaks out to the global scope. It's handy for initialization code and avoiding naming conflicts.
(function() {
const secret = "hidden";
console.log("IIFE runs immediately!");
})();
// console.log(secret); // ReferenceError
A closure happens when a function "remembers" variables from where it was created, even after that outer function is done. It's like a backpack: the inner function carries its surrounding variables wherever it goes.
function counter() {
let count = 0;
return function() {
count++;
return count;
};
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
Before your code runs, JavaScript moves declarations to the top of their scope. This is called hoisting. Function declarations are fully hoisted, so you can call them before they appear. var is hoisted but not its value (you'll get undefined early), while let and const are hoisted but stay uninitialized until their line runs.
console.log(sayHi()); // "Hi!" - works because declaration is hoisted
function sayHi() { return "Hi!"; }
console.log(x); // undefined - var is hoisted but not its value
var x = 5;
The Temporal Dead Zone (TDZ) is the gap between entering a scope and the line where your let or const is declared. If you try to use the variable in that gap, you'll get a ReferenceError. With var, you'd just get undefined instead of an error.
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = "Alice";
DRY stands for "Don't Repeat Yourself." It means you should extract repeated logic into a function so it lives in one place. This makes your code easier to maintain and reduces the chance of bugs when something needs to change.
// Without DRY
console.log("Tax for 100:", 100 * 0.18);
console.log("Tax for 200:", 200 * 0.18);
// With DRY
function calculateTax(amount) { return amount * 0.18; }
console.log("Tax for 100:", calculateTax(100));
console.log("Tax for 200:", calculateTax(200));
Currying breaks a function with multiple arguments into a chain of functions, each taking one argument. Think of it like an assembly line where each step adds one piece. This lets you create reusable, pre-filled versions of functions.
function multiply(a) {
return function(b) {
return a * b;
};
}
const triple = multiply(3);
console.log(triple(4)); // 12
console.log(multiply(2)(5)); // 10
eval() takes a string and runs it as JavaScript code. You should avoid it in most cases because it's slow, hard to debug, and opens the door to security risks with untrusted input.
const code = "2 + 2";
console.log(eval(code)); // 4
// Avoid eval - use safer alternatives instead
Recursion is when a function calls itself to break a big problem into smaller pieces. Think of Russian nesting dolls: each one opens to reveal a smaller one until you reach the last. That last doll is your base case, which tells the function when to stop.
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive call
}
console.log(factorial(5)); // 120
- First-class functions can be stored in variables, passed as arguments, and returned from other functions.
- Higher-order functions take a function as an argument or return one.
- Callback functions are passed into another function and called inside it.
- setTimeout runs a function once after a delay; clearTimeout cancels it.
- setInterval runs a function repeatedly; clearInterval stops it.
- call() and apply() invoke a function with a specific
this; apply uses an array for arguments. - bind() returns a new function with a fixed
this, without calling it immediately. - IIFE is a function that runs immediately and creates a private scope.
- Closures let inner functions remember variables from their outer scope after the outer function has returned.
- Hoisting moves declarations to the top of their scope. Function declarations are fully hoisted;
varis hoisted but not its value. - TDZ is the zone where
let/constvariables exist but cannot be accessed yet. - DRY means "Don't Repeat Yourself" - extract repeated logic into reusable functions.
- Currying converts a multi-argument function into a chain of single-argument functions.
- eval() runs a string as code - avoid it due to performance and security concerns.
- Recursion is when a function calls itself, always with a base case to stop the loop.
What's next? Now that you've seen more powerful function concepts, let's learn how to spot and fix problems in the next tutorial.
Videos for this topic will be added soon.