함수
일련의 과정을 문(statement)로 표현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
함수는 함수 정의를 통해 생성하고, 함수 호출을 통해 실행한다.
함수를 언제 사용하는 것이 좋을까?
동일한 작업을 반복적으로 수행해야 하는 경우에는 정의된 함수를 재사용하는 것이 효율적이다.
함수 리터럴
함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.
- 함수 이름은 생략할 수 있다. 이름이 없는 함수를 무명 함수라고 한다.
- 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다. (함수 내에서 재귀적 호출이 가능하다)
- 함수 리터럴은 평가되어 값을 생성하며, 이 값은 객체이다. 즉, 자바스크립트에서 함수는 객체다.
- 함수는 일급 객체로 일반 객체와 달라 호출할 수 있다.
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
함수 정의
함수 호출 이전에 인수를 전달받을 매개변수, 실행할 문, 반환값을 지정하는 것
함수 정의 방법
1️⃣ 함수 선언문
- 함수 선언문은 함수 리터럴과 달리 함수명을 생략할 수 없다.
function add(x, y) {
return x + y;
}
❗️함수 선언문은 표현식이 아닌 문으로 함수 선언문을 실행하면 undefined가 출력된다.
2️⃣ 함수 표현식
var add = function (x, y) {
return x + y;
};
3️⃣ Function 생성자 함수
var add = new Function('x', 'y', 'return x + y');
4️⃣ 화살표 함수 (ES6)
var add = (x, y) => x + y;
자바스크립트 엔진의 코드 문맥에 따른 해석 방법
1. {}
- { } 단독으로 사용 ➡️ 코드 블록으로 해석된다.
- { } 피연산자로 할당 연산자의 우변에 위치 ➡️ 객체 리터럴로 해석된다.
2. 함수 리터럴 (함수명이 있는 함수 리터럴)
- 함수 리터럴 단독으로 사용 ➡️ 함수 선언문으로 해석된다.
- 함수 리터럴을 변수에 할당 or 피연산자로 사용 ➡️ 함수 리터럴 표현식으로 해석된다.
함수 선언문과 함수 표현식
✅ 함수 선언문이든 함수 표현식이든 함수 표현식처럼 함수 객체를 가리키는 식별자로 함수를 호출한다.
- 함수 이름은 함수 몸체 내에서만 유효한 식별자이다.
- 함수 선언문으로 생성한 함수 객체를 호출하려면 함수 객체를 가리키는 식별자가 필요하다. 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수명과 동일한 이름의 식별자를 암묵적으로 생성해 함수 객체를 할당한다.
✅ 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점과 함수 호이스팅
- 함수 선언문은 런타임 이전에 먼저 실행된다. 다시 말해, 런타임 이전에 함수 객체가 생성되고 암묵적으로 생성된 식별자에 함수 객체가 할당된다.
- 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 따라서, 변수 선언은 런타임 이전에 실행되어 undefined로 초기화된다. 값의 할당은 런타임에 실행되어 식별자에는 함수 객체가 할당된다.
- 함수 선언문은 함수 호이스팅, 함수 표현식은 변수 호이스팅이 발생한다.
- 함수 표현식으로 정의한 함수는 반드시 함수 표현식 이후에 참조 또는 호출해야 한다.
함수 호출
함수를 호출하면 현재의 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다. 이때, 매개변수에 인수가 순서대로 할당되고 함수 몸체의 문들이 실행되기 시작한다.
매개변수와 인수
- 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달해야 할 때, 매개변수를 통해 인수를 전달한다.
- 매개변수는 함수 몸체 내부에서만 참조할 수 있고 함수 몸체 외부에서는 참조할 수 없다.
- 인수가 부족해서 인수가 할당되지 않은 매개변수의 값은 undefined이다.
- 매개변수보다 인수가 더 많은 경우 초과된 인수는 무시된다.
function add(x, y) { // x에는 인수 2가 전달되고 y에는 전달할 인수가 없어 초기화된 상태, undefined이다.
return x + y;
}
console.log(add(2)); // NaN
❗️매개변수에 인수를 전달하지 않았거나 undefined를 전달해도 자바스크립트 엔진은 함수를 아무 문제없이 실행한다. ES6에 도입된 매개변수 기본값을 사용하면 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다.
function add(a = 0, b = 0, c = 0) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
매개변수의 최대 개수
- 매개변수의 순서를 변경하면 인수의 순서도 변경해야 하고, 매개변수의 개수가 많아지면 함수 호출 시 전달해야 할 인수도 많아진다.
- 이는 함수 유지보수를 어렵게 만들기 때문에 최대 3개 이상을 넘지 않는 것을 권장한다.
- 만약 3개 이상의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리하다.
이때, 함수 내부로 전달한 객체를 변경하면 함수 외부의 객체도 변경되는 것에 주의해야 한다.
반환값 (return)
- 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다. 반환문 아래의 문은 실행되지 않고 무시된다.
- return 키워드 뒤에 오는 표현식을 평가해 반환한다. return만 쓰거나 return을 생략하면 undefined가 반환된다.
참조에 의한 전달과 외부 상태의 변경
순수 함수는 외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수를 말한다.
순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 프로그래밍 패러다임을 함수형 프로그래밍이라고 한다.
- 원시 타입의 인수의 값은 복사되어 매개변수로 전달된다. 따라서, 원본이 변경되지 않는다.
- 객체 타입의 인수를 함수 매개변수로 전달하면 참조값이 매개변수에 전달된다. 이는 매개변수와 함수 외부의 어떤 변수가 같은 객체를 가리켜 서로 영향을 받게 된다. (참조에 의한 전달)
- 복잡한 코드에서 이러한 객체의 변경은 추적하기 어렵다.
- 이를 해결하기 위해, 객체를 불변 객체로 사용하여 변경 불가능한 객체로 만든다. 객체의 상태 변경이 필요한 경우에는 깊은 복사(객체 완전 복제)를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
obj.gender = 'female';
}
var num = 100;
var obj = {
name: 'Lee',
gender: 'male'
};
console.log(num); // 100
console.log(obj); // Object {name: 'Lee', gender: 'male'}
changeVal(num, obj);
console.log(num); // 100
console.log(obj); // Object {name: 'Kim', gender: 'female'}
재귀 함수
자기 자신을 호출하는 함수
- 재귀 함수를 사용하면 반복되는 처리를 반복문 없이 구현할 수 있다.
- 반복문보다 재귀 함수로 직관적이고 이해하기 쉬운 구현이 가능할 때 사용하는 것이 좋다.
- 함수 이름은 함수 몸체 내에서만 유효하므로 함수 이름을 사용해 자기 자신을 호출할 수 있다.
중첩 함수 (내부 함수)
함수 내부에 정의된 함수
- 내부 함수는 자신을 포함하는 외부 함수의 변수에 접근할 수 있다. 그러나 외부함수는 내부함수의 변수에 접근할 수 없다.
function parent(param) {
var parentVar = param;
function child() {
var childVar = 'lee';
console.log(parentVar + ' ' + childVar); // Hello lee
}
child();
console.log(parentVar + ' ' + childVar);
// Uncaught ReferenceError: childVar is not defined
}
parent('Hello');
- 내부함수는 외부함수의 외부에서 접근할 수 없다.
function sayHello(name){
var text = 'Hello ' + name;
var logHello = function(){ console.log(text); }
logHello();
}
sayHello('lee'); // Hello lee
logHello('lee'); // logHello is not defined
콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
고차 함수는 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 말한다.
콜백 함수는 고차 함수에 의해 호출된다.
콜백 함수는 비동기 처리(이벤트 처리, 타이머 함수 등), 배열 고차 함수에 활용되는 중요한 패턴이다.
- 콜백 함수가 특정 고차 함수 내부에만 호출된다면 콜백 함수를 익명 함수 리터럴로 정의하면서 곧바로 매개변수로 전달하는 것이 일반적이다.
- 콜백 함수는 고차 함수가 호출될 때마다 콜백 함수 객체가 생성된다.
function repeat (n, f) {
for (var i = 0; i < n; i++) {
f(i); // i를 전달하면서 f를 호출한다.
}
}
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
- 콜백 함수를 여러 함수에서 호출하거나 콜백 함수를 전달받는 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 정의한 후 함수 참조를 고차 함수에 전달하는 편이 효율적이다.
- 함수 선언문은 런타임 이전에 실행되어 단 한번만 콜백 함수 객체가 생성된다.
function repeat (n, f) {
for (var i = 0; i < n; i++) {
f(i); // i를 전달하면서 f를 호출한다.
}
}
var logAll = function (i) {
console.log(i);
};
repeat(5, logAll); // 0 1 2 3 4
var logOdds = function (i) {
if (i % 2) console.log(i);
};
repeat(5, logOdds); // 1 3
함수형 프로그래밍
외부 상태를 변경하는 것을 최소화해 불변성을 지향하는 프로그래밍 패러다임
함수형 프로그래밍은 순수 함수를 통해 부수 효과를 최대한 억제해 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환이다.
자바스크립트는 객체지향 프로그래밍과 더불어 함수형 프로그래밍을 적극적으로 활용한다.
참고
'JavaScript > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
12. 생성자 함수에 의한 객체 생성 (0) | 2022.07.31 |
---|---|
11. 스코프 (Scope)와 전역 변수의 문제점, var/let/const (0) | 2022.07.28 |
9. 원시 값과 객체의 비교 (0) | 2022.07.26 |
8. 객체 리터럴 (0) | 2022.07.25 |
7. 타입 변환과 단축 평가 (0) | 2022.07.23 |