함수란?
함수는 자바스크립트에서 가장 중요한 핵심 개념이다. 스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 등 자바스크립트의 근간을 이루는 개념들이 모두 함수와 깊은 관련이 있다.
프로그래밍 언어의 함수는 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 함수 내부로 입력을 전달받는 변수를 매개변수(parameter), 입력을 인수(argument), 출력을 반환값(return value)이라 한다.
// 함수 정의
function add(x, y) {
return x + y;
}
// 함수 호출
var result = add(2, 5);
console.log(result); // 7
함수의 정의와 호출은 위와 같이 이루어진다.
함수는 함수 정의를 통해 생성하며, 함수 호출에 의해 실행된다. 함수를 호출하면 코드 블록에 담긴 문들이 일괄적으로 실행되고, 실행 결과인 반환값을 반환한다.
함수를 사용하는 이유
- 코드 재사용성: 함수는 필요할 때 여러 번 호출할 수 있어 코드의 재사용 측면에서 매우 유용하다.
- 유지보수 용이성: 코드의 중복을 억제하고 재사용성을 높여 유지보수가 쉬워진다.
- 코드 신뢰성 향상: 실수를 줄이고 코드의 신뢰성을 높인다.
- 코드 가독성 향상: 적절한 함수 이름을 통해 코드의 가독성을 높일 수 있다.
함수 리터럴
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
자바스크립트의 함수는 객체 타입의 값이다. 따라서 함수 리터럴로 함수를 생성할 수 있다. 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.
함수 리터럴의 구성 요소
- 함수 이름
- 함수 이름은 식별자다. 식별자 네이밍 규칙을 준수해야 한다.
- 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.
- 함수 이름은 생략할 수 있다. 이름이 있는 함수를 기명 함수, 이름이 없는 함수를 무명/익명 함수라 한다.
- 매개변수 목록
- 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분한다.
- 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다.
- 매개변수는 함수 몸체 내에서 변수와 동일하게 취급된다.
- 함수 몸체
- 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블록이다.
- 함수 몸체는 함수 호출에 의해 실행된다.
함수는 객체지만 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다. 또한 함수 객체만의 고유한 프로퍼티를 갖는다.
함수 정의
함수 정의란 함수를 호출하기 전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것이다.
- 함수 선언문
- 함수 표현식
- Function 생성자 함수
- 화살표 함수 (ES6)
함수를 정의하는 방법에는 4가지가 있다.
함수 선언문
function add(x, y) {
return x + y;
}
함수 선언문은 위와 같은 형태를 가진다.
함수 선언문의 특징
- 함수 이름을 생략할 수 없다.
- 표현식이 아닌 문이다.
- 자바스크립트 엔진이 암묵적으로 함수 이름과 동일한 식별자를 생성하고 함수 객체를 할당한다.
함수 선언문은 함수 호이스팅이 발생한다. 이는 함수 선언문이 코드의 선두로 끌어올려진 것처럼 동작하는 자바스크립트의 특성이다.
함수 표현식
var add = function(x, y) {
return x + y;
};
함수 표현식은 함수를 값처럼 변수에 할당하는 방식이다.
함수 표현식의 특징
- 함수 이름을 생략할 수 있다 (익명 함수).
- 일급 객체로서의 함수 특성을 활용한다.
- 변수 호이스팅은 발생하지만, 함수 호이스팅은 발생하지 않는다.
함수 선언문은 '표현식이 아닌 문'이고, 함수 표현식은 '표현식인 문'이다.
함수 생성 시점과 함수 호이스팅
// 함수 참조
console.dir(add); // add(x, y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};
함수 선언문과 함수 표현식의 가장 큰 차이점은 함수 생성 시점이다.
- 함수 선언문: 런타임 이전에 함수 객체가 생성되고, 함수 이름과 동일한 식별자에 할당된다.
- 함수 표현식: 변수 선언은 런타임 이전에 실행되지만, 함수 객체는 런타임에 할당된다.
이로 인해 함수 선언문으로 정의한 함수는 선언 이전에 호출할 수 있지만(함수 호이스팅), 함수 표현식으로 정의한 함수는 할당 이전에 호출할 수 없다.
함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야한다는 당연한 규칙을 무시한다. 그렇기에 더글라스 크락포드는 함수 선언문 대신 함수 표현식을 사용할 것을 권장했다.
Function 생성자 함수
var add = new Function('x', 'y', 'return x + y');
Function 생성자 함수를 사용하여 함수를 생성할 수 있다.
하지만 이 방식은 일반적이지 않으며 권장되지 않는다. Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않으며, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작한다.
화살표 함수
const add = (x, y) => x + y;
ES6에서 도입된 화살표 함수는 function 키워드 대신 화살표( => )를 사용하여 더 간결하게 함수를 정의할 수 있다.
화살표 함수의 특징
- 항상 익명 함수로 정의한다.
- 생성자 함수로 사용할 수 없다.
- 기존 함수와 this 바인딩 방식이 다르다.
- prototype 프로퍼티가 없다.
- arguments 객체를 생성하지 않는다.
화살표 함수는 표현뿐만 아니라, 내부 동작 또한 간략화되어 있다.
화살표 함수와 일반 함수의 주요 차이점
this 바인딩
const obj = {
method: function() {
console.log(this); // obj
setTimeout(function() {
console.log(this); // window 또는 global
}, 100);
setTimeout(() => {
console.log(this); // obj
}, 100);
}
};
- 일반 함수는 자신만의 this를 가진다.
- 반면 화살표 함수는 상위 스코프의 this를 그대로 사용한다.
arguments 객체
- 일반 함수는 arguments 객체를 가진다.
- 화살표 함수는 arguments 객체를 가지지 않는다.
생성자 함수
- 일반 함수는 생성자 함수로 사용할 수 있다.
- 화살표 함수는 생성자 함수로 사용할 수 없다.
함수 호출
함수는 함수를 가리키는 식별자와 한 쌍의 소괄호()인 함수 호출 연산자로 호출한다. 함수를 호출하면 현재의 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다.
매개변수와 인수
function add(x, y) {
return x + y;
}
var result = add(2, 3);
console.log(result); // 5
함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 때 매개변수(parameter)를 통해 인수(argument)를 전달한다.
매개변수와 인수의 특징
- 인수는 값으로 평가될 수 있는 표현식이어야 한다.
- 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한이 없다.
- 매개변수는 함수를 정의할 때 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급된다.
- 매개변수의 스코프(유효범위)는 함수 내부이다.
함수는 매개변수와 인수의 개수가 일치하는지 체크하지 않는다. 인수가 부족한 경우 undefined가 할당되고, 초과된 인수는 무시된다.
function add(x, y) {
console.log(arguments);
return x + y;
}
console.log(add(2)); // NaN (2 + undefined)
console.log(add(2, 3, 4)); // 5 (초과된 인수 4는 무시됨)
초과된 인수는 arguments 객체의 프로퍼티로 보관된다.
Rest 파라미터
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
console.log(sum(10, 20, 30)); // 60
Rest 파라미터는 함수의 매개변수를 정의할 때 사용한다. 여러 개의 인수를 하나의 배열로 받을 수 있게 해준다.
Rest 파라미터의 특징
- 함수 정의에서 마지막 매개변수로만 사용할 수 있다.
- 나머지 모든 인수를 배열로 모은다.
function introduce(name, age, ...hobbies) {
console.log(`내 이름은 ${name}이고, ${age}살이다.`);
if (hobbies.length > 0) {
console.log(`취미는 ${hobbies.join(', ')}이다.`);
}
}
introduce('Alice', 30, '독서', '요리', '여행');
// 출력:
// 내 이름은 Alice이고, 30살이다.
// 취미는 독서, 요리, 여행이다.
다른 매개변수와 함께 사용하는 예시는 위와 같다.
스프레드 연산자
스프레드 연산자는 배열이나 객체, 또는 이터러블(iterable) 객체를 개별 요소로 펼친다.
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3
// 배열 병합
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];
console.log(mergedArray); // [1, 2, 3, 4, 5, 6]
// 배열 복사
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
console.log(copiedArray); // [1, 2, 3]
객체에서의 사용:
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2); // { a: 1, b: 2, c: 3 }
// 객체 병합
const object1 = { x: 1, y: 2 };
const object2 = { y: 3, z: 4 };
const mergedObject = { ...object1, ...object2 };
console.log(mergedObject); // { x: 1, y: 3, z: 4 }
// 주의: 같은 키가 있을 경우, 나중에 오는 객체의 값이 우선된다.
배열에서의 사용은 위와 같다.
function greet(first, second, third) {
console.log(`Hello, ${first}, ${second}, and ${third}!`);
}
const names = ['Alice', 'Bob', 'Charlie'];
greet(...names);
// 출력: Hello, Alice, Bob, and Charlie!
함수 호출에서의 사용은 위와 같다.
Rest 파라미터와 스프레드 연산자의 차이점
- Rest 파라미터는 함수 정의에서 사용되며, 여러 인수를 하나의 배열로 모은다.
- 스프레드 연산자는 함수 호출이나 배열/객체 리터럴에서 사용되며, 배열이나 객체를 개별 요소로 펼친다.
이러한 기능들은 함수의 인자 처리를 더욱 유연하게 만들어주며, 배열과 객체를 다룰 때 매우 유용하다. 특히 불변성(immutability)을 유지하면서 데이터를 조작하는 데 도움이 된다.
인수 확인
function add(x, y) {
if (typeof x !== 'number' || typeof y !== 'number') {
throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
}
return x + y;
}
console.log(add(2, 'a')); // TypeError: 인수는 모두 숫자 값이어야 합니다.
자바스크립트는 동적 타입 언어이므로 매개변수의 타입을 사전에 지정할 수 없다. 따라서 함수를 정의할 때 적절한 인수가 전달되었는지 확인할 필요가 있다.
function add(a = 0, b = 0) {
return a + b;
}
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
ES6에서 도입된 매개변수 기본값을 사용하면 인수 체크 및 초기화를 간소화할 수 있다.
매개변수의 최대 개수
// 매개변수가 많은 경우
function createUser(name, age, gender, occupation, location) {
// ...
}
// 객체를 인수로 사용
function createUser(userInfo) {
const { name, age, gender, occupation, location } = userInfo;
// ...
}
createUser({
name: 'John',
age: 30,
gender: 'male',
occupation: 'developer',
location: 'New York'
});
이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다. 따라서 매개변수는 최대 3개 이상을 넘지 않는 것이 좋다. 그 이상의 매개변수가 필요하다면 객체를 인수로 전달하는 것이 유리하다.
반환문
function multiply(x, y) {
return x * y;
// 반환문 이후의 문은 실행되지 않는다.
console.log('이 문은 실행되지 않는다.');
}
console.log(multiply(2, 3)); // 6
함수는 return 키워드와 표현식(반환값)으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환할 수 있다.
반환문의 특징
- 함수의 실행을 중단하고 함수 몸체를 빠져나간다.
- return 키워드 뒤의 표현식을 평가해 반환한다.
- return 키워드 뒤에 표현식이 없으면 undefined를 반환한다.
- 반환문을 생략하면 암묵적으로 undefined를 반환한다.
function multiply(x, y) {
return
x * y; // 세미콜론 자동 삽입으로 인해 undefined가 반환된다.
}
console.log(multiply(2, 3)); // undefined
주의할 점으로, return 키워드와 반환값 사이에 줄바꿈이 있으면 세미콜론 자동 삽입 기능에 의해 의도치 않은 결과가 발생할 수 있다.
참조에 의한 전달과 외부 상태의 변경
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
}
var num = 100;
var person = { name: 'Lee' };
console.log(num); // 100
console.log(person); // {name: "Lee"}
changeVal(num, person);
console.log(num); // 100 (원시 값은 변경되지 않음)
console.log(person); // {name: "Kim"} (객체는 변경됨)
자바스크립트에서 함수의 매개변수는 함수 내부에서 변수와 동일하게 취급된다. 이는 원시 값과 객체를 매개변수로 전달할 때 서로 다른 동작 방식을 보이게 된다.
원시 값 전달
- 원시 값은 값에 의한 전달(pass by value) 방식으로 동작한다.
- 함수 내부에서 매개변수의 값을 변경해도 원본은 영향을 받지 않는다.
객체 전달
- 객체는 참조에 의한 전달(pass by reference) 방식으로 동작한다.
- 함수 내부에서 매개변수로 전달된 객체를 변경하면 원본 객체도 함께 변경된다.
이 예시에서 num은 원시 값이므로 함수 내부에서 변경되어도 외부의 원본 값에는 영향을 주지 않는다. 반면 person은 객체이므로 함수 내부에서의 변경이 외부의 원본 객체에도 영향을 미친다.
이러한 동작 방식은 다음과 같은 특징을 가진다.
원시 값(Primitive values)
- 변경 불가능한(immutable) 값이다.
- 재할당을 통해서만 값을 변경할 수 있다.
객체(Objects)
- 변경 가능한(mutable) 값이다.
- 재할당 없이도 직접 속성을 변경할 수 있다.
함수가 외부 상태를 변경하는 부수 효과(side effect)는 프로그램의 복잡성을 증가시키고 가독성을 해칠 수 있다. 이는 상태 변화를 추적하기 어렵게 만들어 버그의 원인이 될 수 있다.
이러한 문제를 해결하기 위한 방법들
- 불변 객체(Immutable objects) 사용
- 객체를 마치 원시 값처럼 변경 불가능하게 만든다.
- 상태 변경이 필요한 경우, 깊은 복사(deep copy)를 통해 새로운 객체를 생성하고 재할당한다.
- 순수 함수(Pure functions) 사용
- 외부 상태를 변경하지 않고, 외부 상태에 의존하지도 않는 함수를 만든다.
- 이는 함수형 프로그래밍의 핵심 개념 중 하나이다.
function changeVal(primitive, obj) {
primitive += 100;
return { ...obj, name: 'Kim' }; // 새로운 객체 생성
}
var num = 100;
var person = { name: 'Lee' };
console.log(num); // 100
console.log(person); // {name: "Lee"}
var result = changeVal(num, person);
console.log(num); // 100 (원시 값은 변경되지 않음)
console.log(person); // {name: "Lee"} (원본 객체도 변경되지 않음)
console.log(result); // {name: "Kim"} (새로운 객체가 생성됨)
이러한 방식을 통해 예측 가능하고 안정적인 코드를 작성할 수 있다.
순수 함수의 장점
- 테스트 용이성: 입력과 출력만으로 함수를 테스트할 수 있다.
- 병렬 처리: 외부 상태에 의존하지 않아 병렬 실행이 가능하다
- 캐싱: 동일 입력에 대해 항상 같은 출력을 반환하므로 결과를 캐싱할 수 있다.
- 불변성: 데이터 변경 없이 새로운 데이터를 생성하여 예측 가능성을 높인다.
함수형 프로그래밍에서는 이러한 순수 함수를 사용하여 부수 효과를 최소화하고 프로그램의 안정성을 높인다.
다양한 함수의 형태
자바스크립트에서는 다양한 형태의 함수를 사용할 수 있다. 각 함수 형태는 특정 상황에서 유용하게 활용될 수 있으며, 코드의 구조와 실행 방식에 영향을 미친다.
즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
}());
즉시 실행 함수(IIFE, Immediately Invoked Function Expression)는 함수를 정의함과 동시에 즉시 실행되는 함수를 말한다.
즉시 실행 함수의 특징
- 단 한 번만 호출되며 재호출이 불가능하다.
- 주로 익명 함수를 사용한다.
- 함수를 그룹 연산자(...)로 감싸야 한다.
즉시 실행 함수를 사용하는 이유
- 전역 스코프를 오염시키지 않는다.
- 변수나 함수 이름의 충돌을 방지할 수 있다.
- private한 변수를 만들 수 있다.
var result = (function (a, b) {
return a * b;
}(3, 5));
console.log(result); // 15
즉시 실행 함수도 일반 함수처럼 값을 반환하거나 인수를 전달받을 수 있다.
// 기본 형태
(function() { /* ... */ })();
// 함수 표현식 뒤에 호출 연산자
(function() { /* ... */ }());
// 연산자를 이용한 방법
!function() { /* ... */ }();
+function() { /* ... */ }();
void function() { /* ... */ }();
// 화살표 함수를 사용한 방법
(() => { /* ... */ })();
또한, 다양한 패턴으로 작성할 수도 있다. 이러한 패턴들은 모두 함수를 즉시 실행시키는 목적으로 사용되며, 상황에 따라 적절한 패턴을 선택할 수 있다.
재귀 함수
function factorial(n) {
if (n <= 1) return 1; // 탈출 조건
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
재귀 함수는 자기 자신을 호출하는 함수를 말한다.
재귀 함수의 특징
- 반복적인 처리를 위해 사용된다.
- 반드시 탈출 조건이 있어야 한다.
- 스택 오버플로우에 주의해야 한다.
재귀 함수의 장단점
- 장점: 복잡한 알고리즘을 간단하고 명확하게 표현할 수 있다.
- 단점: 무한 반복에 빠질 위험이 있고, 깊은 재귀는 스택 오버플로우를 일으킬 수 있다.
그리고 이러한 단점에 있다시피 재귀 함수는 직관적이지만, 깊은 재귀 호출은 스택 오버플로우를 일으킬 수 있다. 이를 해결하기 위한 방법 중 하나가 꼬리 재귀 최적화이다.
// 꼬리 재귀
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
꼬리 재귀는 재귀 호출이 함수의 마지막 연산으로 수행되는 형태이다. 일부 언어와 엔진에서는 꼬리 재귀를 자동으로 최적화하여 스택 오버플로우를 방지한다. 하지만 자바스크립트는 현재 대부분의 환경에서 이를 지원하지 않으므로, 큰 입력값에 대해서는 일반적인 반복문을 사용하는 것이 안전하다.
즉, 대부분의 재귀 함수는 반복문으로 구현 가능하므로, 재귀 함수 사용 시 성능과 가독성을 매우 고려해야 한다.
중첩 함수
function outer() {
var x = 1;
function inner() {
var y = 2;
console.log(x + y);
}
inner();
}
outer(); // 3
중첩 함수(또는 내부 함수)는 함수 내부에 정의된 함수를 말한다.
중첩 함수의 특징
- 외부 함수 내에서만 호출 가능하다.
- 주로 외부 함수를 돕는 헬퍼 함수 역할을 한다.
- 외부 함수의 변수에 접근할 수 있다(클로저).
※ 주의사항: ES6부터는 if문이나 for문 등의 코드 블록 내에서도 함수 정의가 가능하지만, 호이스팅으로 인한 혼란을 피하기 위해 권장되지 않는다.
콜백 함수
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i);
}
}
repeat(5, function(i) {
console.log(i);
});
콜백 함수는 다른 함수의 인자로 전달되어 나중에 호출되는 함수를 말한다.
콜백 함수의 특징
- 함수의 매개변수를 통해 다른 함수 내부로 전달된다.
- 고차 함수에 의해 호출된다.
- 비동기 처리나 이벤트 처리 등에 많이 사용된다.
콜백 함수의 장점
- 코드의 재사용성을 높인다.
- 비동기 작업을 처리하기 용이하다.
- 함수의 확장성을 높인다.
※ 주의사항: 콜백 지옥(callback hell)에 빠지지 않도록 주의해야 한다. 이를 해결하기 위해 promise나 async/await 등의 방법을 사용할 수 있다.
콜백 지옥(Callback Hell)
asyncOperation1(function(result1) {
asyncOperation2(result1, function(result2) {
asyncOperation3(result2, function(result3) {
// ... 계속됨
});
});
});
콜백 지옥(Callback Hell)은 비동기 작업을 연속적으로 수행할 때 발생하는 문제이다.
// Promise 사용
asyncOperation1()
.then(result1 => asyncOperation2(result1))
.then(result2 => asyncOperation3(result2))
.then(result3 => {
// ...
})
.catch(error => {
// 에러 처리
});
// async/await 사용
async function performOperations() {
try {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2(result1);
const result3 = await asyncOperation3(result2);
// ...
} catch (error) {
// 에러 처리
}
}
이를 해결하기 위해 Promise와 async/await가 도입되었다. 이러한 방식은 코드의 가독성을 높이고 에러 처리를 용이하게 한다.
조금 더 다른 예제를 살펴보자면 다음과 같은 내용이 있다.
// 간단한 비동기 작업을 시뮬레이션하는 함수
function simulateAsyncOperation(callback) {
setTimeout(() => {
console.log("비동기 작업 완료");
callback();
}, 1000);
}
// 콜백 함수 사용
simulateAsyncOperation(() => {
console.log("콜백 함수 실행");
});
console.log("메인 스크립트 실행 완료");
// 출력 순서:
// 1. "메인 스크립트 실행 완료"
// 2. (1초 후) "비동기 작업 완료"
// 3. "콜백 함수 실행"
콜백 함수는 다른 함수에 인자로 전달되어, 그 함수의 실행이 끝난 후에 실행되는 함수이다. 그리고 위의 예시에서 simulateAsyncOperation 함수는 비동기 작업을 시뮬레이션한다.
asyncOperation1((result1) => {
asyncOperation2(result1, (result2) => {
asyncOperation3(result2, (result3) => {
// 계속해서 중첩...
});
});
});
콜백 함수는 이 비동기 작업이 완료된 후에 실행되는데... 여러 비동기 작업을 연속해서 처리해야 할 때 콜백 지옥이 발생할 수 있다. 이에 대한 자세한 해결책은 아래의 내용에서 다루겠다.
Promise
Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체이다. 그리고 아래의 예시에서 simulateAsyncOperation 함수는 Promise를 반환한다.
// Promise를 반환하는 함수
function simulateAsyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5; // 50% 확률로 성공 또는 실패
if (success) {
resolve("작업 성공");
} else {
reject("작업 실패");
}
}, 1000);
});
}
// Promise 사용
simulateAsyncOperation()
.then((result) => {
console.log(result); // "작업 성공"
})
.catch((error) => {
console.error(error); // "작업 실패"
})
.finally(() => {
console.log("작업 완료");
});
console.log("메인 스크립트 실행 완료");
.then()은 성공 시 실행되고, .catch()는 실패 시 실행된다. .finally()는 성공이든 실패든 항상 실행된다.
fetchUserData(userId)
.then(userData => fetchUserPosts(userData.id))
.then(posts => displayPosts(posts))
.catch(error => console.error("Error:", error));
이렇듯 Promise를 사용하면 여러 비동기 작업을 연쇄적으로 처리할 수 있다.
Async/Await
async function fetchAndDisplayUserData(userId) {
try {
const userData = await fetchUserData(userId);
const posts = await fetchUserPosts(userData.id);
displayPosts(posts);
} catch (error) {
console.error("Error:", error);
}
}
// 함수 호출
fetchAndDisplayUserData(123);
console.log("메인 스크립트 실행 완료");
async/await는 Promise를 더 쉽게 사용할 수 있게 해주는 문법적 설탕(syntactic sugar)이다.
- async 키워드는 함수가 항상 Promise를 반환하도록 한다.
- await 키워드는 Promise가 resolve될 때까지 실행을 일시 중지한다.
- try/catch 구문으로 에러를 처리한다.
async/await를 사용하면 비동기 코드를 마치 동기 코드처럼 작성할 수 있어, 가독성이 크게 향상된다.
순수 함수와 비순수 함수
순수 함수는 외부 상태에 의존하지 않고 변경하지 않는 함수이며, 비순수 함수는 그 반대다.
// (순수 함수)
function add(a, b) {
return a + b;
}
순수 함수의 특징
- 동일한 인수에 대해 항상 동일한 값을 반환한다.
- 외부 상태를 변경하지 않는다.
- 부수 효과(side effect)가 없다.
// (비순수 함수)
var count = 0;
function incrementCount() {
return ++count;
}
비순수 함수의 특징
- 외부 상태에 의존하거나 변경한다.
- 동일한 인수에 대해 다른 값을 반환할 수 있다.
- 부수 효과가 있다.
순수 함수를 사용하면 코드의 예측 가능성과 테스트 용이성이 높아지며, 함수형 프로그래밍의 기본 원칙이 된다. 그러나 실제 애플리케이션에서는 순수 함수만으로 모든 기능을 구현하기 어려우므로, 상황에 따라 적절히 사용해야 한다.
예상 문제
자바스크립트에서 함수를 정의하는 방법은 몇가지가 있나요?
- 자바스크립트에서 함수를 정의하는 방법은 크게 네 가지가 있습니다.
- 첫째, 함수 선언문을 사용하는 방법이 있습니다.
- 둘째, 함수 표현식을 이용하는 방법이 있습니다.
- 셋째, Function 생성자 함수를 사용하는 방법이 있습니다.
- 마지막으로 ES6에서 도입된 화살표 함수를 사용하는 방법이 있습니다.
- 이 중에서 함수 선언문과 함수 표현식이 가장 일반적으로 사용되며, 화살표 함수는 ES6 이후 많이 사용되고 있습니다. Function 생성자 함수는 잘 사용되지 않습니다.
함수 선언문과 함수 표현식은 어떤 차이가 있나요?
- 함수 선언문과 함수 표현식의 주요 차이점은 호이스팅과 관련이 있습니다.
- 함수 선언문으로 정의된 함수는 자바스크립트 엔진에 의해 코드 실행 전에 먼저 생성되어 어디서든 호출할 수 있습니다. 이를 함수 호이스팅이라고 합니다.
- 반면, 함수 표현식은 변수에 할당되는 형태로, 변수 선언만 호이스팅되고 함수 할당은 실행 시점에 이루어집니다. 따라서 함수 표현식으로 정의된 함수는 해당 코드 라인 이후에만 호출할 수 있습니다
- 또한, 함수 선언문은 반드시 함수명이 필요하지만, 함수 표현식은 익명 함수로도 사용할 수 있습니다.
즉시 실행 함수(IIFE)에 대해 알고 있나요? 알고 있다면 아는 내용에 대해 말해보세요.
- 함수 정의와 동시에 즉시 호출되는 함수를 IIFE, 즉시 실행 함수라고 합니다. IIFE는 단 한 번만 호출되며 다시 호출할 수 없습니다.
- 예를 들면 (function() { /* 코드 */ })(); 이런 형태입니다. 반드시 그룹 연산자( ( ) )로 감싸야 합니다.
- 함수 이름이 없는 것이 일반적이고 함수 이름을 지정할 수는 있으나, 어찌되었든 간에 함수를 다시 호출하는 것은 불가능합니다.
- IIFE의 주요 목적은 전역 스코프를 오염시키지 않고 독립적인 스코프를 만드는 것입니다. 이를 통해 변수나 함수의 이름 충돌을 방지하고, private한 변수를 만들 수 있습니다. 또한, IIFE는 모듈 패턴을 구현할 때 자주 사용됩니다.
- ES6의 모듈 시스템이 도입되기 전에는 IIFE를 사용해 모듈과 유사한 기능을 구현했습니다. IIFE는 한 번만 실행되고 재사용할 수 없다는 특징이 있어, 초기화 코드나 일회성 작업에 적합합니다.
'🧱 프론트엔드 주제 > JavaScript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 14장 전역 변수의 문제점 (0) | 2024.07.09 |
---|---|
[모던 자바스크립트 Deep Dive] 13장 스코프 (0) | 2024.07.07 |
[모던 자바스크립트 Deep Dive] 11장 원시 값과 객체의 비교 (0) | 2024.06.27 |
[모던 자바스크립트 Deep Dive] 10장 객체 리터럴 (0) | 2024.06.25 |
[모던 자바스크립트 Deep Dive] 9장 타입 변화와 단축 평가 (1) | 2024.06.20 |