[JavaScript] 함수 (2)
Lpla
·2021. 5. 2. 21:46
이전 글에서 이어집니다.
6. 참조에 의한 전달과 외부 상태의 변경
엄연히 말해 자바스크립트에서 참조에 의한 전달은 존재하지 않고 값에 의한 전달만이 존재한다.
하지만 편의상 전달되는 값의 종류에 따라 값에 의한 전달과 참조에 의한 전달로 구분하여 부르기로 한다.
이 때 참조에 의한 전달은 다른 프로그래밍 언어에서 말하는 그것과 정확히 일치하지 않는다.
// 매개변수 primitive는 원시값을 전달받고, 매개변수 obj는 객체를 전달받는다.
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"}
원시 타입 인수는 값 자체가 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 값을 변경해도 원본은 훼손되지 않는다.
하지만 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.
이러한 현상은 객체가 변경할 수 있는 값이며 참조에 의한 전달 방식이기 때문에 발생하는 현상이다.
7. 다양한 함수의 형태
7-1. 즉시 실행 함수
함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행 함수라고 한다.
즉시 실행 함수는 단 한번만 호출된다.
// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
}());
즉시 실행 함수는 일반적으로 익명 함수로 사용한다.
기명 함수로 사용할 수 있지만 함수 선언문이 아닌 함수 리터럴로 인식되기 때문에 다시 호출할 수 없는 건 마찬가지다.
// 기명 즉시 실행 함수
(function foo() {
var a = 3;
var b = 5;
return a * b;
}());
foo(); // ReferenceError: foo is not defined
즉시 실행 함수는 주로 두 가지 형태로 쓰인다.
(function () {
// ...
}());
(function () {
// ...
})();
즉시 실행 함수 내에 코드를 모아 두면 변수나 함수 이름의 충돌을 방지할 수 있다.
7-2. 재귀 함수
자기 자신을 호출하는 것을 재귀 호출이라 한다. 재귀 호출을 수행하는 함수를 재귀 함수라 한다.
일반적으로 1부터 10까지 출력하는 함수는 아래와 같다.
function countdown(n) {
for (var i = n; i >= 0; i--) console.log(i);
}
countdown(10);
재귀 함수를 사용하면 아래와 같다.
function countdown(n) {
if (n < 0) return;
console.log(n);
countdown(n - 1); // 재귀 호출
}
countdown(10);
재귀 함수는 자신을 무한 재귀 호출한다. 따라서 재귀 호출을 멈출 수 있는 탈출 조건을 반드시 만들어야 한다.
재귀 함수는 반복문 없이 반복 처리를 구현할 수 있지만 무한 반복에 빠질 위험이 있어 잘 사용하지 않는다.
7-3. 중첩 함수
함수 내부에 정의된 또 다른 함수를 중첩 함수라 한다.
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
ES6 이전에는 코드 최상위 혹은 함수 내부에서만 정의할 수 있었지만 ES6부터는 if 문이나 for 문 등의 코드 블럭 내부에서도 정의할 수 있다.
하지만 혼란이 발생할 수 있으므로 좋은 방식은 아니다.
7-4. 콜백 함수
function repeat(n) {
for (var i = 0; i < n; i++) {
console.log(i);
}
}
repeat(5); // 0 1 2 3 4
위 repeat 함수는 console.log(i)를 호출하고 i가 1씩 증가하는 작업을 전달받은 숫자만큼 반복한다.
하지만 이 반복문은 확장성이 낮다. 만약 홀수만 호출하도록 하려면 함수를 새로 만들어야 한다.
function repeat2(n) {
for (var i = 0; i < n; i++) {
if (i % 2) {
console.log(i);
}
}
}
repeat2(5); // 1 3
이것은 좋은 방식이 아니다.
중복되는 공통 로직은 하나로 통일하고 상황에 따라 달라지는 로직은 함수화하여 유연한 구조를 갖는 함수가 좋은 방식이다.
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i);
}
}
var logAll = function (i) {
console.log(i);
};
var logOdds = function (i) {
if (i % 2) console.log(i);
};
repeat(5, logAll); // 0 1 2 3 4
repeat(5, logOdds); // 1 3
위 repeat 함수는 더 이상 내부 로직에 의존하지 않고 외부에서 로직의 일부분을 함수로 받아 수행한다.
이처럼 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며, 매개 변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수라고 한다.
logAll 함수와 logOdds 함수는 콜백 함수, repeat 함수가 고차 함수에 해당한다.
콜백 함수는 고차 함수에 의해 호출되며 이때 고차 함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.
(단, 모든 콜백 함수가 고차 함수에 의해 호출되는 것은 아니다.)
콜백 함수는 비동기 처리에 활용되는 중요한 패턴이다.
// 콜백 함수를 사용한 이벤트 처리
document.getElementById('myButton').addEventListener('click', function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
setTimeout(function () {
console.log('1초 경과');
}, 1000);
7-5. 순수 함수와 비순수 함수
함수형 프로그래밍에서는 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수라 하고, 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수를 비순수 함수라 한다.
순수 함수
var count = 0;
function increase(n) {
return ++n;
}
count = increase(count);
console.log(count); // 1
count = increase(count);
console.log(count); // 2
비순수 함수
var count = 0;
function increase() {
return ++count;
}
increase();
console.log(count); // 1
increase();
console.log(count); // 2
출처
모던 자바스크립트 Deep Dive(2020, 이웅모)