WeniVooks

검색

JavaScript 에센셜

다양한 함수

1. 콜백함수

콜백 함수는 다른 함수의 매개변수로 전달되어 함수 내부에서 실행되는 함수를 의미합니다. 콜백 함수를 이용하면 함수의 실행 순서를 제어할 수 있어 배열 메서드, 이벤트 처리, 비동기 처리 등 다양한 상황에서 사용됩니다.

배열 메서드에 콜백 함수를 전달하여 사용하는 예시를 살펴보겠습니다. 배열의 map 메서드는 배열의 각 요소에 대해 콜백 함수를 실행하고, 콜백 함수의 반환값으로 새로운 배열을 생성합니다.

function square(x) { return x ** 2; } let arr = [1, 2, 3, 4, 5]; console.log(arr.map(square)); arr.map((x) => x ** 2);

콜백함수를 이용하면 코드의 흐름을 제어할 수 있습니다. 다음 예시는 setTimeout 함수를 이용하여 3초 뒤에 print 함수를 실행하는 코드입니다. 다른 함수에 인자로 콜백함수를 전달하여 특정 시점에 실행되도록 할 수 있습니다.

function timer(time, callback) { setTimeout(callback, time); } function print(text) { console.log(text); } timer(3000, print('3초 뒤에 출력됩니다.'));

일반적으로 화살표 함수를 이용하여 콜백 함수를 작성합니다. 화살표 함수는 함수를 간결하게 작성할 수 있어 콜백 함수로 많이 사용됩니다.

let arr = [1, 2, 3, 4, 5]; arr.map((x) => x ** 2);

First-class 언어
First-class(일급) 언어는 함수를 값처럼 다룰 수 있는 언어를 말합니다. 자바스크립트는 함수를 값처럼 다룰 수 있는 특징을 가지고 있습니다. 변수에 할당하거나, 다른 함수의 매개변수로 전달하거나, 다른 함수의 반환값으로 사용할 수 있습니다. 함수를 값으로 취급하기 때문에 콜백 함수를 사용할 수 있습니다. 이러한 기능을 지원하는 언어들을 first class 언어라고 하며 Python, Ruby등이 이러한 언어군에 속합니다.

2. 재귀함수

재귀함수(Recursive function)는 함수가 자기 자신을 호출하는 함수를 뜻합니다. 재귀 함수를 이용하면 반복문을 사용하지 않고도 반복적인 작업을 수행할 수 있습니다. 즉, 반복문으로 해결할 수 있는 문제는 재귀함수로 해결할 수 있습니다.

다음은 반복문으로 구현된 팩토리얼 함수입니다. 팩토리얼 함수는 1부터 n까지의 곱을 구하는 함수입니다.

function factorial(n) { let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } factorial(5);

팩토리얼 함수를 재귀 함수로 구현하면 다음과 같이 작성할 수 있습니다. 팩토리얼 함수에서는 n이 1보다 작거나 같을 때까지 자기 자신을 호출합니다.

function factorial(n) { if (n <= 1) { return 1; } return n * factorial(n - 1); } factorial(5); // factorial(5) == 5 * factorial(4) == 5 * 24 // factorial(4) == 4 * factorial(3) == 4 * 6 // factorial(3) == 3 * factorial(2) == 3 * 2 // factorial(2) == 2 * factorial(1) == 2 * 1 // factorial(1) == 1

재귀 함수를 작성할 때, 재귀 호출이 무한히 반복되지 않도록 종료 조건을 반드시 설정해야 합니다. 종료 조건이 없으면 함수가 무한 호출되어 스택 오버플로우가 발생할 수 있습니다. 위 코드의 팩토리얼 함수에서 종료 조건은 n <= 1 입니다. n이 1보다 작거나 같을 때 함수를 더 이상 호출하지 않고 1을 반환하여 함수가 종료되도록 설정합니다.

다음은 1부터 n까지의 합을 구하는 sigma 함수입니다. sigma 함수는 1부터 n까지의 합을 구하는 함수입니다.

function sigma(n) { let result = 0; for (let i = 1; i <= n; i++) { result += i; } return result; } sigma(5);

sigma 함수를 재귀함수로 구현해보겠습니다.

function sigma(n) { if (n <= 0) { return 0; } return n + sigma(n - 1); } sigma(5); // sigma(5) == 5 + sigma(4) == 5 + 10 // sigma(4) == 4 + sigma(3) == 4 + 6 // sigma(3) == 3 + sigma(2) == 3 + 3 // sigma(2) == 2 + sigma(1) == 2 + 1 // sigma(1) == 1 + sigma(0) == 1 + 0

재귀 함수는 반복문을 사용하지 않고도 반복적인 작업을 수행할 수 있습니다. 복잡한 문제를 간단하게 표현할 수 있어 코드의 가독성을 높일 수 있습니다. 하지만 함수 호출이 많아져 성능이 떨어질 수 있으니 성능을 고려하여 사용해야 합니다.

3. 즉시 실행함수

즉시 실행 함수는 함수를 선언함과 동시에 실행하는 함수입니다. 선언과 동시에 실행하기 때문에 외부에서 함수를 호출할 수 없습니다. 따라서 외부에서 호출할 필요가 없는 경우에 사용합니다.

(function() { /* 코드 */ })(); (function() { /* 코드 */ }());

일반 함수처럼 인자를 전달하고, 반환값을 받을 수 있습니다. 내부에서 사용되는 변수는 함수 스코프를 가지기 때문에 메모리를 효율적으로 관리할 수 있습니다. 변수의 스코프를 제한하여 전역 네임스페이스의 오염을 방지합니다.

const result = (function func(a, b) { return a * b; })(3, 5); console.log(result); console.log(a); // ReferenceError: a is not defined console.log(b); // ReferenceError: b is not defined

즉시 실행 함수는 주로 모듈 패턴을 구현할 때 사용됩니다. 모듈 패턴은 전역 변수를 사용하지 않고 모듈 내부에서만 사용할 수 있는 변수를 만들어 코드 충돌을 방지하는 패턴입니다.

const counter = (function () { let count = 0; return { increase() { return count++; }, decrease() { return count--; }, getCount() { return count; }, }; })(); counter.increase(); counter.increase(); console.log(counter.getCount()); console.log(count); // ReferenceError: count is not defined

일반적으로 즉시 실행 함수는 함수 이름을 작성하지 않는 익명 함수로 사용합니다. 이름을 가지는 기명 함수로 작성할 수도 있지만, 함수의 호출은 불가합니다.

// 기명 즉시 실행 함수 (function func() { let a = 3; let b = 5; return a * b; })(); func(); // ReferenceError: func is not defined

4. 생성자 함수 (Constructor)

자바스크립트에서 객체를 생성하는 방식은 대표적으로 두 가지가 있습니다. 하나는 중괄호({})를 사용한 객체 리터럴 표현식, 그리고 나머지가 생성자 함수를 이용한 방식입니다. 생성자 함수는 사용자 정의 객체를 생성할 때 사용되는 함수입니다. 생성자 함수를 이용하면 동일한 구조의 객체를 여러 개 생성할 수 있습니다. 또한 객체지향 프로그래밍에서 배울 프로토타입을 이용하여 메서드를 공유하여 메모리 효율을 높일 수 있습니다.

let book = {
  도서명: 'JavaScript 에센셜',
  가격: 15000,
  저자: ['웨이드', '하티'],
  출판일: '2024.08.24',
};
// 책이 100권이라면 모든 객체를 일일이 작성해야 하는 문제가 발생
 
function Book(도서명, 가격, 저자, 출판일) {
  this.도서명 = 도서명;
  this.가격 = 가격;
  this.저자 = 저자;
  this.출판일 = 출판일;
}
 
let book1 = new Book('HTML/CSS 에센셜', 20000, ['로지', '지지'], '2024.7.31');
let book2 = new Book('견고한 Python', 300000, ['라이캣'], '2023.11.30');
let book3 = new Book('웹/네트워크/HTTP', 0, ['라이캣'], '2024.4.26');
 
console.log(book1, book2, book3);
let book = {
  도서명: 'JavaScript 에센셜',
  가격: 15000,
  저자: ['웨이드', '하티'],
  출판일: '2024.08.24',
};
// 책이 100권이라면 모든 객체를 일일이 작성해야 하는 문제가 발생
 
function Book(도서명, 가격, 저자, 출판일) {
  this.도서명 = 도서명;
  this.가격 = 가격;
  this.저자 = 저자;
  this.출판일 = 출판일;
}
 
let book1 = new Book('HTML/CSS 에센셜', 20000, ['로지', '지지'], '2024.7.31');
let book2 = new Book('견고한 Python', 300000, ['라이캣'], '2023.11.30');
let book3 = new Book('웹/네트워크/HTTP', 0, ['라이캣'], '2024.4.26');
 
console.log(book1, book2, book3);

생성자 함수는 일반 함수와 구분하기 위해 함수 이름의 첫 글자를 대문자로 작성하는 파스칼 케이스를 관습적으로 사용합니다. 생성자 함수를 호출할 때는 new 연산자와 함께 호출합니다. 생성자 함수를 호출하면 객체가 생성되고, 이때 생성된 객체를 인스턴스(instance)라고 합니다. new 연산자는 생성자 함수의 this가 인스턴스를 가리키도록 합니다.

내부적으로는 다음과 같이 동작합니다. 생성자 함수를 호출하면 빈 객체가 생성되고, this에 바인딩됩니다. 그리고 함수 내부의 코드가 실행되면서 this에 프로퍼티가 추가됩니다. 마지막으로 this가 반환됩니다.

function Book(도서명, 가격, 저자, 출판일) { // this = {} this.도서명 = 도서명; this.가격 = 가격; this.저자 = 저자; this.출판일 = 출판일; // return this }

이때 new 연산자를 사용하지 않으면 일반 함수로 동작합니다. 이 때는 this가 전역 객체인 window를 가리키게 됩니다. 이는 전역 객체에 프로퍼티를 추가하는 문제가 생길 수 있으니 생성자 함수를 호출할 때는 반드시 new 연산자를 사용해야 합니다.

function Book(도서명, 가격, 저자, 출판일) { this.도서명 = 도서명; this.가격 = 가격; this.저자 = 저자; this.출판일 = 출판일; } let book1 = Book('HTML/CSS 에센셜', 20000, ['로지', '지지'], '2024.7.31'); let book2 = Book('견고한 Python', 300000, ['라이캣'], '2023.11.30'); // book1과 book2에는 어떤 값이 출력될까요? console.log(book1); console.log(book2); // 전역 객체의 프로퍼티 console.log(도서명, 가격, 저자, 출판일);

이처럼 생성자 함수를 통해 생성된 객체는 같은 프로퍼티메서드를 가지게 됩니다. 이와 관련해서 객체지향 프로그래밍 챕터에서 좀 더 자세히 얘기해보겠습니다.

9.1 파라미터와 아규먼트9.3 스코프와 클로저