모던 자바스크립트 Deep Dive 12장 함수를 읽고 정리한 글이다.
1. 함수란?
- 함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
- 함수 내부로 입력을 전달 받는 변수를 매개 변수, 입력을 인수, 출력을 반환값이라 함
- 함수는 값이며, 여러 개 존재할 수 있으므로 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용
- 함수는 함수 정의를 통해 생성, 함수 호출을 통해 실행
2. 함수를 사용하는 이유
- 함수는 필요할 때 재사용이 가능하므로 코드의 재사용이라는 측면에서 매우 유용
- 코드의 중복을 억제하고 재사용성을 높이는 함수는 유지보수의 편의성을 높이고, 실수를 줄여 코드의 신뢰성을 높이는 효과가 있음
- 적절한 함수 이름은 함수의 내부 코드를 이해하지 않고도 함수의 역할을 파악할 수 있어 코드의 가독성을 향상시킴
3. 함수 리터럴
- 자바스크립트의 함수는 객체 타입의 값이므로, 함수 리터럴로 생성 가능
- 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성
- 함수 리터럴은 평가되어 값을 생성하며, 이 값은 객체. 따라서 함수는 객체다.
var f = function add(x, y){
return x + y;
}
4. 함수 정의
- 함수 정의란 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것
- 정의된 함수는 자바스크립트에 의해 평가되어 함수 객체가 된다
함수 정의 방식
1. 함수 선언문
- 함수 선언문은 함수 이름을 생략할 수 없음
- 함수 선언문은 표현식이 아닌 문이어서 변수에 할당할 수 없음
- 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당
- 이후 함수 객체를 가리키는 식별자로 함수를 호출
function add(x, y){
return x + y;
}
2. 함수 표현식
- 함수 리터럴로 생성한 함수 객체를 변수에 할당하는 함수 정의 방식을 함수 표현식이라 함
- 함수 리터럴의 함수 이름은 생략할 수 있으며, 이러한 함수를 익명 함수라 함
- 함수 표현식은 표현식인 문이므로 변수에 할당
var add = function (x, y){
return x+y
}
함수 생성 시점과 함수 호이스팅
- 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출할 수 있지만 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다 → 각각의 함수 생성 시점이 다르기 때문
- 함수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행됨. 자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당
- 따라서 런타임에는 이미 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태이므로 함수 선언문 이전에 함수를 참조하고 호출할 수 있음
- 이를 함수 호이스팅이라 함
- 함수 표현식은 변수 할당문이 실행되는 시점, 즉 런타임에 평가되므로 이때 함수 객체가 됨
- 따라서 함수 표현식으로 함수를 정의하면 변수 호이스팅이 발생하여 함수 표현식 이전에 함수 참조시 undefined로 평가됨
- 함수 표현식으로 정의한 함수는 반드시 함수 표현식 이후에 참조 또는 호출해야함
3. 함수 생성자 함수
- 자바스크립트가 기본 제공하는 빌트인 함수인 Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환
- 해당 방식은 일반적이지 않으며, 바람직하지 않음
- 클로저 생성하지 않고, 다른 함수와 다르게 동작
var add = new Function('x', 'y', 'return x+y');
4. 화살표 함수
- ES6 도입
- function 키워드 대신 화살표를 사용해 익명 함수로 정의
var add = (x, y) => x+ y;
5. 함수 호출
5.1 매개변수와 인수
- 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우 매개변수를 통해 인수를 전달
- 인수는 값으로 평가될 수 있는 표현식이어야 함
- 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한 없음
- 매개변수는 함수를 정의할 때 선언하며, 함수 내부에서 변수와 동일하게 취급
- 즉, 함수가 호출되면 함수 몸체에서 암묵적으로 매개변수가 생성되고 undefined로 초기화 된후 인수가 순서대로 할당됨
- 매개변수의 스코프(유효 범위)는 함수 내부
- 함수를 호출할 때 매개변수 개수만큼 인수를 전달하는 것이 일반적이지만, 그렇지 않은 경우에도 에러가 발생하지 않음
- 매개변수보다 인수가 더 많은 경우 초과된 인수는 무시됨
5.2 인수 확인
- 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
- 따라서 부적절한 호출을 사전에 방지할 수 없고 에러는 런타임에 발생하게 됨
- 타입스크립트와 같은 정적 타입을 선언할 수 있는 상위 확장을 도입해서 컴파일 시점에 부적절한 호출을 방지할 수 있게 하는 것도 하나의 방법
- arguments객체를 통해 인수 개수를 확인하거나 인수가 전달되지 않는 경우 단축 평가를 사용해 매개변수에 기본값을 할당하는 것도 하나의 방법
5.3 매개변수의 최대 개수
- 매개변수의 최대 개수는 없지만 최대 3개 이상 넘지 않는 것을 권장
- 만약 그 이상의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리
- 객체를 인수로 사용하는 경우 프로퍼티 키만 정확히 지정하면 매개변수 순서를 신경쓰지 않아도 되기 때문
5.4 반환문
- 함수는 return 키워드와 표현식(반환값)으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환할 수 있음
- 반환문의 역할
- 함수의 실행을 중단하고 함수 몸체를 빠져나간다
- return 키워드 뒤에 오는 표현식을 평가해 반환한다.
- 반환문은 생략할 수 있으며, 이때 함수는 함수 몸체의 마지막 문까지 실행한 후 암묵적으로 undefined를 반환
6. 참조에 의한 전달과 외부 상태의 변경
- 원시 타입 인수는 값 자체가 복사되어 매개변수에 전달(값에 의한 전달)되기 때문에 함수 몸체에서 그 값을 변경(재할당)해도 원본은 훼손되지 않음
- 객체 타입 인수는 참조 값이 복사되어 매개변수에 전달(참조에 의한 전달)되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손됨
- 이처럼 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워짐
- 해결 방법 중 하나는 객체를 불변 객체로 만들어 사용하는 것. 객체의 상태 변경이 필요한 경우 깊은 복사를 통해 새로운 객체를 생성하고 재할당을 통해 교체할 수 있음
7. 다양한 함수의 형태
7.1 즉시 실행 함수
- 함수 정의와 동시에 즉시 호출되는 함수를 칭함
- 즉시 실행 함수는 단 한번만 호출되며 다시 호출할 수 없음
- 즉시 실행 함수는 반드시 ()로 감싸야하며, 그렇지 않으면 에러 발생
- 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고, 인수를 전달할 수도 있음
- 즉시 실행 함수 내에 코드를 모아두면 혹시 있을 수도 있는 변수나 함수 이름 충돌을 방지할 수 있음
(function () {
}()) ;
(function () {
})();
!function () {
}();
+function () {
}
7.2 재귀 함수
- 재귀 호출을 수행하는 함수를 칭함
- 재귀 함수는 반복되는 처리를 위해 사용
- 재귀 함수는 자신을 무한 재귀 호출하므로, 재귀 함수 내에서 재귀 호출을 멈출 수 있는 탈출 조건을 반드시 만들어야 함
7.3 중첩 함수
- 함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 하고, 중첩 함수를 포함하는 함수를 외부 함수라 함
- 중첩함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 수행
- 호이스팅으로 인해 혼란이 발생할 수 있어 if문이나 for문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않음 → 스코프와 클로저에 깊은 관련이 있음
7.4 콜백 함수
- 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라 하며, 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차 함수라 함
- 콜백 함수도 고차 함수에 전달되어 헬퍼 함수의 역할을 함. 중첩 함수와 비교했을 때 자유롭게 교체할 수 있다는 장점이 있음
- 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출
- 콜백 함수가 고차 함수 내부에만 호출된다면 콜백 함수를 익명 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적
repeat(5, function (i) {
if (i % 2) console.log(i);
});
- 콜백함수는 비동기 처리(이벤트 처리, 타이머 함수 등)에 활용되는 중요한 패턴
document.getElementById( 'myButton').addEventListener('click', function () {
console.log( 'button clicked!');
});
- 콜백 함수는 비동기 처리뿐 아니라 배열 고차 함수에서도 사용
var res = [1, 2, 3].map(function (item) {
return item * 2;
});
console.log(res);
7.5 순수 함수와 비순수 함수
- 순수 함수는 어떤 외부 상태에도 의존하지 않고 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성해 반환
- 일반적으로 최소 하나 이상의 인수를 전달 받음
- 함수의 외부 상태를 변경하지 않음
- 비순수 함수는 외부 상태에 따라 반환값이 달라지며 외부 상태에 의존하거나 외부 상태를 변경하는 함수
- 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워지며, 함수 외부 상태의 변경을 지양하는 순수 함수를 사용하는 것이 좋음