Function declarations are hoisted with their full body. This means it can be called before its definition:
add(2, 3); // works!
function add (x, y){
return x + y;
}
Note: Here x, y are parameters, and 2, 3 are arguments.
myAdd(2, 3); // ❌ ReferenceError: Cannot access 'myAdd' before initialization
const myAdd = function (x, y){
return x + y;
};
Only the variable declaration (const myAdd) is hoisted — not the function body.
Difficult debugging message. Error stacks will show: at <anonymous>
const myAdd = function add (n){
if (n <= 1) return 1;
return n * add(n - 1); // this works because "add" is the function’s internal name
};
The function has an internal name: add. It is not visible as a variable in the outer scope — it's only available inside the function itself.
This is useful for: Self-reference (recursion); Better debugging (stack traces)
Not function declaration, but an expression → so invoked immediately
(function greet() {
alert("Hello World");
})();
Even though the function executes immediately and isn’t callable outside, named IIFEs (here: greet) are useful for more descriptive debugging.
(() => {
console.log("run immediately");
})();
Avoid polluting global variables
Run setup code once
Initialize modules or configurations
Syntax:
param => expression → returns the expression
param => { statements } → must use return
const add = (x, y) => x +y;
const mul = (x, y) => {
let z = x * y;
return z;
};
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const myCar = new Car("Honda", "HRV", 2020);
let str = JSON.stringify(myCar);
It's dynamic. How this is set depends entirely on the call site:
normal call → undefined (in strict mode) /global object (window in browser, global in Node)
object method → the object
call/apply → explicitly set
bind → permanently bound
In JavaScript, every function is a "Function" object.
call, apply, and bind are built-in methods of the Function.prototype, meaning all functions inherit them.
Syntax: func.call(thisArg, arg1, arg2, ...)
Calls the function immediately
Sets this to thisArg
Arguments are passed one by one
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: "Alice" };
greet.call(person, "Hello", "!"); // Hello, Alice!
this inside greet is now person.
Syntax: func.apply(thisArg, [arg1, arg2, ...])
Works just like call()
The difference: arguments are passed as an array
greet.apply(person, ["Hi", "!!!"]); // Hi, Alice!!!
Use apply() when you have arguments as an array
Syntax: const newFunc = func.bind(thisArg, arg1, arg2, ...)
Returns a new function
Sets this permanently to thisArg
Does not call it immediately
const greetAlice = greet.bind(person, "Hey");
greetAlice("?"); // Hey, Alice?
Great for callbacks, event handlers, or storing a function with fixed this.
A closure is formed when a function remembers the variables from the place where it was created, even after that outer function has finished executing.
Outer Function Scope
├─ let count = 0 ← captured (used by inner function)
├─ let temp = 99 ← ignored (not used)
↓
Inner Function
└─ remembers: { count }
Here variable count still persists — though it is not visible to outside (encapsulated)
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
function makeAdder(x) {
return function(y) {
return x + y;
}
}
const add5 = makeAdder(5);
console.log(add5(10)); // 15
function greet(name = "Anonymous") {
console.log(`Hello, ${name}`);
}
greet(); // Hello, Anonymous
greet("Alice"); // Hello, Alice
greet(undefined); // Hello, Anonymous
Key Rule: Defaults apply only when argument is undefined.
Defaults can be expressions and can depend on earlier parameters:
function f(a = 1, b = a + 1) {
console.log(a, b);
}
f(); // 1 2
Rest parameters allow a function to accept unlimited arguments, captured into an array.
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
Key Rule: Must be the last parameter.
Spread vs Rest:
...rest → collects items into array (input side)
...spread → expands array into items (output side)
JavaScript does not have native named parameters, but the idiom is: Use an object as the argument → destructure in the function.
function createUser({ name, age, role = "user" }) {
console.log(name, age, role);
}
createUser({
name: "Alice",
age: 25
});
// "Alice" 25 "user"
Benefits:
Order doesn’t matter.
Default values per property.
Self-documenting calls:
function f({ a, b, ...rest }) {
console.log(a, b, rest);
}
f({ a: 1, b: 2, c: 3, d: 4 }); // rest = {c:3, d:4}