함수 내부에 정의된 함수를 중첩 함수(nested function)
또는 내부 함수(inner function)
라 한다. 중첩 함수를 포함하는 함수는 외부 함수(outer function)
이라 한다. 중첩 함수는 외부 함수 내부에서만 호출할 수 있고, 일반적으로 중첩함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수(helper function)
의 역할을 한다.
function outer() {
let x = 1;
// 중첩 함수
function inner() {
let y = 2;
//외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
ES6부터 함수 정의는 문이 위치할 수 있는 문맥이라면 어디든지 가능. 함수 선언문의 경우 ES6 이전에는 코드 최상위 또는 다른 함수 내부에서만 정의할 수 있었으나 ES6부터는 if문이나 for문 등의 코드 블록 내에서도 정의할 수 있다.
단, 호이스팅으로 인해 혼란이 발생할 수 있으므로 if 문이나 for문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않다.
function repeat(n) {
// i를 출력한다.
for (let i = 0; i < n; i++) console.log(i);
}
repeat(5); // 0 1 2 3 4
repeat 함수는 매개변수를 통해 전달받은 숫자만큼 반복하며 console.log(i)
를 호출한다. 이때 repeat함수는 console.log(i)
에 강하게 의존하고 있어 다른 일을 할 수 없다. 따라서 만약 repeat 함수의 반복문 내부에서 다른 일을 하고 싶다면 함수를 새롭게 정의해야한다.
function repeat(n) {
// i를 출력한다.
for (let i = 0; i < n; i++) console.log(i);
}
repeat(5); // 0 1 2 3 4
function repeat(n) {
// i를 출력한다.
for (let i = 0; i < n; i++) {
// i가 홀수일 때만 출력
if(i % 2) console.log(i);
}
}
repeat(5); // 1 3
위 예제의 함수들은 반복하는 일은 변하지 않고 공통적으로 수행하지만 반복하면서 하는 일의 내용은 서로 다르다. 함수의 일부분만 다르기 때문에 매번 함수를 새롭게 정의 해야한다. 이 문제는 함수를 합성하는 것으로 해결가능하다. 함수의 변하지 않는 공통 로직은 미리 정의해 두고, 경우에 따라 변경되는 로직은 추상화해서 함수 외부에서 함수 내부로 전달하는 방법이다.
//외부에서 전달 받은 f를 n만큼 반복 호출한다.
function repeat(n, f) {
for (let i = 0; i < n; i++){
f(i);
}
}
const logAll = function (i) {
console.log(i);
};
//반복 호출할 함수를 인수로 전달한다.
repeat(5, logAll); // 0 1 2 3 4
const logOdds = function (i) {
if (i % 2) console.log(i);
};
//반복 호출할 함수를 인수로 전달한다.
repeat(5, logOdds);// 1 3
위 repeat 함수는 경우에 따라 변경되는 일을 함수 f로 추상화
했고 이를 외부에서 전달 받는다.자바스크립트의 함수는 일급 객체로 취급되기에 함수의 매개변수를 통해 함수를 전달이 가능하다. repeat 함수는 더 이상 내부 로직에 강력히 의존하지 않고 외부에서 로직의 일부분을 함수로 전달받아 수행하므로 더욱 유연한 구조를 갖게 된다.
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(callback function)
라고 하며, 매개 변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(Higher-Order function)
라고 한다.
중첩 함수와 비슷하지만 중첩 함수는 내부 로직이 고정되어 있어서 교체하기 곤란하지만 콜백 함수는 함수 외부에서 고차함수 내부로 주입되기에 자유롭게 교체가 가능하다.
콜백 함수는 함수형 프로그래밍 패러다임뿐만 아니라 비동기 처리(이벤트 처리, Ajax 통신, 타이머 함수 등)에 활용되는 중요한 패턴이다.
document.getElementById('myButton').addEventListener('click' function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력한다.
set
Timeout(function (){
console.log('메세지');
}, 1000);
콜백 함수는 비동기 처리뿐 아니라 배열 고차 함수에서도 사용된다. 자바스크립트에서 배열은 사용 빈도가 매우 높은 자료구조이고 배열을 다룰 때 배열 고차 함수는 매우 중요한 역할을한다.
//콜백 함수를 사용하는 고차 함수 map
let res = [1, 2, 3].map(function (item) {
return item * 2;
});
console.log(res); // [2, 4, 6]
// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
return item % 2;
});
console.log(res); // [1, 3]
// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
return acc + cur;
}, 0);
console.log(res); // 6
순수 함수와 비순수 함수
함수형 프로그래밍에서는 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수(pure function)
라 하고, 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수를 비순수 함수(impure function)
라고 한다.
let count = 0; // 현재 카운트를 나타내는 상태
// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
count = increase(count);
console.log(count); // 2
let count = 0; // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화한다.
// 비순수 함수
function increase() {
return ++count;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
increase();
console.log(count); // 1
increase();
console.log(count); // 2
위 예제와 같이 인수를 전달받지 않고 함수 내부에서 외부 상태를 직접 참조하면 외부 상태에 의존하게 되어 반환값이 변할 수 있고, 외부 상태도 변경할 수 있으므로 비순수 함수가 된다.
비순수 함수는 코드의 복잡성을 증가시키기에 최대한 줄이는 것은 부수 효과를 최대한 억제하는 것과 같다.
이로써 모던 자바스크립트 딥다이브 책의 함수 부분을 전부 살펴보았다.
몰랏던 부분과 명확하게 알지 못 했던 부분들이 많이 채워진것 같다.
아직 완벽히는 아니지만 또 읽고 읽다보면 언젠가는 완벽히 이해되지 않을까 한다.
다시 공부하면서 느끼지만 생각보다 재미가 더 있다.. 😍