[모던 자바스크립트 Deep Dive] 12장 함수
12장 함수
1. 함수란 ?
일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정한 것
함수 정의를 통해 생성하고, 함수 호출로 실행한다.
2. 함수를 사용하는 이유
- 코드의 재사용성
- 유지보수의 편의성
- 코드의 신뢰성/가독성
3. 함수 리터럴
📌 구성 요소 : 함수 이름, 매개변수 목록, 함수 몸체
일반 객체는 호출할 수 없지만, 함수는 호출할 수 있으며 고유의 프로퍼티를 갖는다.
함수는 "객체" 이다.
4. 함수 정의
1) 함수 선언문
function add(x,y) {
return x + y;
}
console.dir(add); // console.log와 다르게 객체의 프로퍼티까지 출력
console.log(add(2,3));
✅ 함수 선언문은 표현식이 아닌 "문" 이다. (undefined가 출력됨)
함수 선언문은 표현식이 아닌 문 이지만 함수 리터럴 표현식과 형태가 동일하기 때문에 변수에 할당될 수 있다.
하지만, 함수를 생성하는 내부 동작에 차이가 있다.
함수 선언문 | 함수 리터럴 | |
생성 여부 | 함수 객체를 생성 | |
호출 방식 | 함수 이름으로 호출 가능 | 함수 이름으로 호출 불가능 (함수 몸체 내에서만 참조 가능) |
왜 함수 선언문은 함수 이름으로 호출이 가능할까 ?
자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
📌 함수는 함수 이름으로 호출하는 것이 아닌 함수 객체를 가르키는 식별자로 호출한다.
* 따라서 함수 선언문으로 생성한 함수는 자바스크립트 엔진이 암묵적으로 생성한 식별자로 호출을 하게 된다.
2) 함수 표현식
- 값의 성징을 갖는 객체를 일급 객체라고 함
- 자바스크립트의 함수는 일급 객체다.
- 함수 리터럴로 생성한 함수 객체를 변수에 할당하는 방식을 함수 표현식이라고 함
- 함수 리터럴의 함수 이름은 생략 가능하다.
var add = function (x, y) {
return x + y;
};
✅ 함수 생성 시점과 호이스팅
함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 가능하다.
(함수 표현식으로 정의한 함수는 불가능, 하지만 참조는 가능하다)
왜 why ? 모든 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다.
따라서 함수 선언문 이전에 함수를 참조/호출 가능한 현상을 호이스팅 이라고 한다.
👉 함수 표현식으로 함수를 정의 했을 때는 함수 호이스팅이 아닌 변수 호이스팅에 의해 undefined로 평가된다.
console.dir(add); // f add(x,y)
console.dir(sub); // undefined
console.log(add(2,4)); // 6
console.log(sub(2,4)); // Error
// 함수 선언문
function add(x,y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x-y;
};
* 함수 표현식을 쓸 때는 반드시 함수 표현식 이후 참조/호출 해야 한다.
하지만 함수 호이스팅은 위의 규칙을 무시하기 때문에 더그라스 크락포드는 함수 표현식을 더 권장한다.
3) Function 생성자 함수
var add = new Function('x', 'y', 'return x + y');
* new 연산자 없이 호출 도 가능하다.
해당 방식은 일반적이지도 않고, 바람직하지 않다. (클로저 생성x, 일반 함수와 다르게 동작)
4) 화살표 함수
- es6에 새롭게 도입된 익명 함수
const add = (x, y) => x + y;
- 생성자 함수로 사용 불가능, this 바인딩 방식 다록, prototype 프로퍼티가 없고, arguments 객체를 생성하지 않음 !
5. 함수 호출
1) 매개변수와 인수
- 인수 : 값으로 평가될 수 있는 표현식, 개수와 타입에 제하니 없다
- 매개변수 : 함수를 정의할 때 선언, 함수 몸체 내부에서 변수와 동일하게 취급된다.
- 함수 호출 시 함수 몸체 내에서 암묵적으로 매개변수가 생성되고 undefined로 초기화 된 후 인수가 순서대로 할당됨
- 매개변수와 인수의 개수가 동일하지 않아도 에러는 나지 않는다. 부족할 시 초기값이 undefined 값이기 때문
초과 시에는 무시되지만 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
2) 인수 확인
- 자바스크립트 경우 타입 체크를하지 않기에 함수 정의 시 적절한 인수가 전달되었는지 확인 필요
- es6에서 도입된 매개변수 기본값을 사용하면 인수 체크 및 초기화를 간편히 할 수 있다.
function add(a = 0, b = 0, c= 0) {
return a + b + c;
}
3) 매개변수의 최대 개수
- 일반적으로 최대 3개 이상을 넘지 않는 것을 권장 (그 이상이면 객체를 이용)
* 이상적인 함수는 한가지 일만 해야하며 가급적 작게 만들어야 한다.
4) 반환문
- 함수의 실행을 중단하고 함수 몸체를 빠져나감
- return 키워드 뒤에 오는 표현식을 평가해 반환한다. (기본값 undefiend)
* 함수 바깥에서 사용하면 문법 에러가 발생한다.
6. 참조에 의한 전달과 외부 상태의 변경
- 매개변수로 받는 원시 값은 값 자체가 복사되고, 객체는 참조 값이 복사된다.
- 객체의 변경을 추적하려면 옵저버 패턴 등을 통해 대응 해야한다.
- 또다른 해결 방법으로는 객체를 불변 객체로 만들어 동작하게 하는 방법이 있다.
7. 다양한 함수의 형태
1) 즉시 실행 함수(IIFE)
- 함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행함수라고 한다
(function () {
var a = 4;
return a;
}());
- 보통은 익명함수 지만, 기명함수로 실행할 수 도 있다. (하지만 해당 식별자로 다시 호출 불가능)
- 즉시 호출 함수의 다양한 형태(보통 첫 번째 방식을 많이 쓴다)
(function () {
// ...
}());
(function () {
// ...
})();
!function () {
// ...
}();
+function () {
// ...
}();
2) 재귀 함수
- 함수가 자기 자신을 호출하는 것을 재귀 호출이라고 함
- 반드시 종료 조건이 있어야 한다.
- 함수 표현식으로 생성시 해당 식별자로 내부에서 호출해야 한다.
3) 중첩 함수
- 함수 내부에 정의된 함수를 중첩 함수라고 한다.
- 중첩 함수는 외부 함수 내부에서만 호출 될 수 있다.
4) 콜백 함수
- 함수의 매개변수를 통해 다른 함수의 내부로 전달 되는 함수를 콜백 함수라고 한다.
- 매개 변수를 통해 함수의 외부에서 콜백 함수로 전달받은 함수를 고차 함수라고 한다.
function repeat(n, f) {
for (var i=0; i<n; i++) {
f(i);
}
}
var logAll = function (i) {
console.log(i);
};
repeat(4, logAll);
- 고차 함수는 매개변수를 통해 전달받는 콜백 함수의 호출 시점을 결정해서 호출가능하다.
- 따라서 콜백 함수는 고차 함수에 의해 호출되며, 고차 함수는 필요에 의해 콜백 함수에 인수를 전달 할 수 있다.
- 비동기 처리, 배열 고차 함수 등에서 자주 사용되는 패턴이다.
5) 순수 함수와 비순수 함수
- 순수 함수 : 외부 상태에 의존하지도 않고 변경하지도 않는 부수효과가 없는 함수
- 비순수 함수 : 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수
- 순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수다.
따라서 인수를 변경하지 않는 것이 일반적이다.
var count = 0;
// 순수 함수
function increase(n) {
return ++n;
}
// 비순수 함수
function decrease() {
return --count;
}
count = increase(count); // 1
decrease(); // count = 0;
비 순수 함수는 외부 상태를 변경하므로 상태 변화를 추적하기 어렵다.
👉 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수효과를 최고화해서 불변성을 지향하는 프로그래밍 패러다임.