JavaScript Functions | Declarations, Arrows, Callbacks, and Closures

JavaScript Functions | Declarations, Arrows, Callbacks, and Closures

이 글의 핵심

Hands-on guide to JavaScript functions: how to define them, pass data, use closures, and avoid common this pitfalls.

Introduction

What is a function?

A function is a block of code that performs a task. In JavaScript, functions are first-class values: you can assign them to variables, pass them as arguments, and return them from other functions.


1. Ways to define functions

Function declaration

function add(a, b) {
    return a + b;
}

console.log(add(10, 20));  // 30

greet();  // hoisted

function greet() {
    console.log("Hello!");
}

Function expression

const subtract = function(a, b) {
    return a - b;
};

const factorial = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
};

// multiply();  // ReferenceError if called before assignment

const multiply = function(a, b) {
    return a * b;
};

Arrow functions (ES6+)

const add = (a, b) => {
    return a + b;
};

const add = (a, b) => a + b;

const square = x => x * x;

const greet = () => console.log("Hello!");

const makePerson = (name, age) => ({ name, age });
console.log(makePerson("Alice", 25));

const complexFunc = (a, b) => {
    const sum = a + b;
    const product = a * b;
    return { sum, product };
};

Arrow function notes:

  1. Concise syntax
  2. Lexical this (inherits from enclosing scope)
  3. No arguments object—use rest parameters (...args)
  4. Not constructible—cannot use new

When to use them: array callbacks, short utilities, when lexical this is what you want.

Comparison

DeclarationExpressionArrow
HoistingYesNo*No*
thisdynamicdynamiclexical
argumentsyesyesno
newyesyesno

* Assignments are not hoisted like declarations.


2. Parameters and return values

Default parameters

function greet(name = "guest") {
    return `Hello, ${name}!`;
}

function createArray(length = 10, fill = 0) {
    return Array(length).fill(fill);
}

Rest parameters

function sum(...numbers) {
    return numbers.reduce((acc, num) => acc + num, 0);
}

function introduce(greeting, ...names) {
    return `${greeting}, ${names.join(", ")}!`;
}

Destructuring parameters

function printUser({ name, age, city = "Seoul" }) {
    console.log(`${name} (${age}, ${city})`);
}

function getMinMax([first, ...rest]) {
    return {
        min: Math.min(first, ...rest),
        max: Math.max(first, ...rest)
    };
}

Returning multiple values

function getCoordinates() {
    return [10, 20];
}
const [x, y] = getCoordinates();

function getUserInfo() {
    return { name: "Alice", age: 25, city: "Seoul" };
}
const { name, age } = getUserInfo();

3. Higher-order functions

Functions as arguments

function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}

repeat(3, console.log);
repeat(3, i => console.log(`iteration ${i}`));

Functions that return functions

function makeMultiplier(factor) {
    return function(x) {
        return x * factor;
    };
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

const makeMultiplier = factor => x => x * factor;

Array higher-order methods

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(x => x * 2);
const evens = numbers.filter(x => x % 2 === 0);
const sum = numbers.reduce((acc, x) => acc + x, 0);

const result = numbers
    .filter(x => x % 2 === 0)
    .map(x => x * 2)
    .reduce((acc, x) => acc + x, 0);

4. Closures

What is a closure?

A closure is a function that remembers variables from the scope where it was created.

function makeCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter = makeCounter();
console.log(counter());  // 1
console.log(counter());  // 2

const counter2 = makeCounter();
console.log(counter2());  // 1

Private state

function createBankAccount(initialBalance) {
    let balance = initialBalance;

    return {
        deposit(amount) {
            if (amount > 0) balance += amount;
            return balance;
        },
        withdraw(amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                return balance;
            }
            return null;
        },
        getBalance() {
            return balance;
        }
    };
}

More examples

function makeAdder(x) {
    return y => x + y;
}

function memoize(fn) {
    const cache = {};
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) return cache[key];
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

5. this binding

Ordinary functions

const person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, ${this.name}.`);
    }
};

person.greet();

const greetFunc = person.greet;
// greetFunc();  // loses `this` in non-strict sloppy mode / undefined in strict

Arrow functions and this

const person = {
    name: "Alice",
    hobbies: ["reading", "sports", "coding"],
    printHobbies2: function() {
        this.hobbies.forEach(hobby => {
            console.log(`${this.name}'s hobby: ${hobby}`);
        });
    }
};

person.printHobbies2();

bind, call, apply

const person = { name: "Alice" };

function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

greet.call(person, "Hello", "!");
greet.apply(person, ["Hello", "."]);

const boundGreet = greet.bind(person);
boundGreet("Hi", "~");

6. Callbacks

Basics

function processArray(arr, callback) {
    const result = [];
    for (let item of arr) {
        result.push(callback(item));
    }
    return result;
}

Async callbacks

console.log("start");

setTimeout(() => {
    console.log("after 2s");
}, 2000);

console.log("end");

setTimeout(() => {
    console.log("1s");
    setTimeout(() => {
        console.log("2s");
        setTimeout(() => console.log("3s"), 1000);
    }, 1000);
}, 1000);

7. IIFE

(function() {
    console.log("IIFE");
})();

(() => {
    console.log("arrow IIFE");
})();

const result = (function() {
    return 10 + 20;
})();

Module-style IIFE

const counterModule = (function() {
    let count = 0;
    return {
        increment() { count++; return count; },
        decrement() { count--; return count; },
        getCount() { return count; }
    };
})();

8. Recursion

function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

function sumArray(arr) {
    if (arr.length === 0) return 0;
    return arr[0] + sumArray(arr.slice(1));
}

9. Practical examples

Utilities: chunk, flatten, unique

function chunk(arr, size) {
    const result = [];
    for (let i = 0; i < arr.length; i += size) {
        result.push(arr.slice(i, i + size));
    }
    return result;
}

function flatten(arr) {
    return arr.reduce((acc, item) =>
        acc.concat(Array.isArray(item) ? flatten(item) : item), []);
}

function unique(arr) {
    return [...new Set(arr)];
}

Composition

const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

Debounce and throttle

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func(...args), delay);
    };
}

function throttle(func, delay) {
    let lastCall = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastCall >= delay) {
            lastCall = now;
            func(...args);
        }
    };
}

10. Common mistakes

Arrow functions as methods

const person = {
    name: "Alice",
    greet: () => {
        console.log(this.name);  // wrong: lexical this
    }
};

Closures in loops with var

for (var i = 0; i < 3; i++) {
    setTimeout(function() { console.log(i); }, 100);
}

for (let i = 0; i < 3; i++) {
    setTimeout(function() { console.log(i); }, 100);
}

11. Exercises

Currying

function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) return fn(...args);
        return (...nextArgs) => curried(...args, ...nextArgs);
    };
}

Pipeline

function pipe(...fns) {
    return function(x) {
        return fns.reduce((acc, fn) => fn(acc), x);
    };
}

Summary

Key points

  1. Definitions: declarations hoist; expressions and arrows do not behave like declarations.
  2. Parameters: defaults, rest, destructuring.
  3. Higher-order: pass/return functions; map/filter/reduce.
  4. Closures: encapsulation and memoization.
  5. this: dynamic in ordinary methods; lexical in arrows—choose accordingly.

Best practices

  1. Prefer arrows for short callbacks when this should come from outside.
  2. Write small, pure functions when possible.
  3. Name functions clearly; limit parameter count.

Next steps

  • JavaScript arrays and objects
  • JavaScript async programming
  • JavaScript classes

  • JavaScript async programming | Promises and async/await