함수란?
일련의 과정을 문(statements)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
- 매개변수, 인수, 반환값으로 구성되어 있다.
함수는 함수 정의를 통해 생성하고 함수를 실행하기 위해서는 함수를 호출해야 한다.
함수를 사용하는 이유
- 코드의 재사용
- 동일한 작업을 반복적으로 수행할 때 코드를 중복하여 작성하기 보다 미리 정의된 함수를 재사용하는 것이 효율적이다.
- 유지보수의 편의성, 코드의 신뢰성
- 코드를 중복해서 여러번 작성하면 중복된 횟수만큼 수정해야 한다.
- 따라서 코드 수정 시간 증가하고 실수할 가능성이 높아진다.
- 코드의 가독성
- 함수 또한 이름을 붙일 수 있으며 적절한 함수 이름은 함수의 내부 코드를 이해하지 않고도 함수의 역할을 파악할 수 있게 돕는다.
함수 리터럴
- function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성
- 함수 이름
- 함수 이름은 식별자이며 식별자 네이밍 규칙을 준수해야 한다.
- 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.
- 함수 이름은 생략할 수 있다.
- 이름이 있는 함수 → 기명 함수
- 이름이 없는 함수 → 무명/익명 함수
- 매개변수 목록
- 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분한다.
- 각 매개변수는 함수를 호출할 때 지정한 인수가 순서대로 할당된다.
- 매개변수는 함수 몸체 내에서 변수와 동일하게 취급된다.
- 함수 몸체
- 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블록
- 함수 호출에 의해 실행된다.
함수는 객체이며 함수 리터럴도 평가되어 값을 생성한다.
일반 객체는 호출할 수 없지만 함수는 호출할 수 있으며 함수 객체만의 고유한 프로퍼티를 갖는다는 차이점이 있다.
함수 정의
함수를 정의하는 방식에는 4가지가 있다.
1. 함수 선언문
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 참조
// console.dir은 console.log와는 달리 함수 객체의 프로퍼티까지 출력한다.
// 단, Node.js 환경에서는 console.log와 같은 결과가 출력된다.
console.dir(add); // ƒ add(x, y)
// 함수 호출
console.log(add(2, 5)); // 7함수 선언문은 함수 리터럴과 형태가 동일하지만 함수 이름을 생략할 수 없다.
// 함수 선언문은 함수 이름을 생략할 수 없다.
function (x, y) {
return x + y;
}
// SyntaxError: Function statements require a function name함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.
// 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.
// 하지만 함수 선언문이 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) {
return x + y;
};
// 함수 호출
console.log(add(2, 5)); // 7위의 코드는 함수 선언문을 변수에 할당한 것으로 보이지만, 자바스크립트 엔진이 코드의 문맥을 고려하여 함수 리터럴 표현식으로 해석한 것이다.
기명 함수 리터럴은 함수 선언문 또는 함수 리터럴 표현식으로 해석될 가능성이 있다.
- 함수 이름이 있는 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석
- 함수 리터럴을 변수에 할당하거나 피연산자로 사용하면 함수 리터럴 표현식으로 해석
함수 선언문이나 함수 표현식을 사용하면 함수 객체가 생성되는 것은 동일하다.
그러나 호출에 차이가 있다.
- 함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.
- 따라서 함수 외부에서 함수 이름으로 함수를 참조할 수 없다.
- 자바스크립트 엔진은 함수 선언문을 해석해 함수 객체를 생성한다.
- 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 함수 객체를 바인딩한다.
함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.
2. 함수 표현식
자바스크립트의 함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다.
// 함수 표현식
var add = function (x, y) {
return x + y;
};
console.log(add(2, 5)); // 7함수 리터럴의 함수 이름은 생략할 수 있고 함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.
호출은 함수 객체를 가리키는 식별자로 호출해야 한다.
함수 이름은 함수 몸체 내부에서만 유효한 식별자다.
// 기명 함수 표현식
var add = function foo(x, y) {
return x + y;
};
// 함수 객체를 가리키는 식별자로 호출
console.log(add(2, 5)); // 7
// 함수 이름으로 호출하면 ReferenceError가 발생한다.
// 함수 이름은 함수 몸체 내부에서만 유효한 식별자다.
console.log(foo(2, 5)); // ReferenceError: foo is not defined함수 선언문은 “표현식이 아닌 문”이고 함수 표현식은 “표현식인 문”이다.
3. 함수 생성 시점과 호이스팅
// 함수 참조
console.dir(add); // ƒ add(x, y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수는 생성 시점이 다르다.
- 함수 선언문
- 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다.
- 런타임 이전에 함수 객체가 생성된다.
- 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 함수 객체를 할당한다.
- 따라서 함수 선언문 이전에 함수를 참조할 수 있고 호출할 수 있다.
- 함수 호이스팅: ****함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 것
- 함수 표현식
- 함수 표현식으로 함수를 정의하면 함수 호이스팅이 아닌 변수 호이스팅이 발생한다.
- 따라서 함수 표현식 이전에 함수를 참조하면 undefined로 평가된다.
- 이때 함수를 호출하면 undefined를 호출하는 것과 마찬가지이므로 타입 에러가 발생한다.
함수 호이스팅은 함수 호출 이전에 반드시 함수를 선언해야 한다는 규칙을 무시하므로 함수 표현식을 사용하는 것을 권장한다.
4. Function 생성자 함수
Function 생성자 함수에 new 연산자를 함께 호출하면 함수 객체를 생성해서 반환한다.
new 연산자 없이 호출해도 결과는 동일하다.
Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않는 등, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작한다.
var add1 = (function () {
var a = 10;
return function (x, y) {
return x + y + a;
};
})();
console.log(add1(1, 2)); // 13
var add2 = (function () {
var a = 10;
return new Function("x", "y", "return x + y + a;");
})();
console.log(add2(1, 2)); // ReferenceError: a is not defined5. 화살표 함수
ES6에서 도입된 화살표 함수는 function 키워드 대신 화살표 => 를 사용해 좀 더 간략한 방법으로 함수를 선언할 수 있다.
항상 익명 함수로 정의한다.
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7- 화살표 함수는 생성자 함수로 사용할 수 없다.
- 기존 함수와 this 바인딩 방식이 다르다.
- prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다.
함수 호출
1. 매개변수와 인수
매개변수는 함수를 정의할 때 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급된다.
- 함수가 호출되면 함수 몸체 내에서 암묵적으로 매개변수가 생성되고 일반 변수와 마찬가지로 undefined로 초기화된 이후 인수가 순서대로 할당된다.
- 매개 변수의 스코프는 함수 내부다. 외부에서 참조할 수 없다.
- 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다.
- 인수가 부족 → 매개변수 undefined
- 인수 초과 → 초과된 인수 무시, arguments 객체의 프로퍼티로 보관
2. 인수 확인
- 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.
- 자바스크립트는 동적인 언어이기 때문에 자바스크립트 함수는 매개변수 타입을 사전에 지정할 수 없다.
적절한 인수가 전달되었는지 확인하고 싶을 때
-
함수 몸체 내에서 인수의 타입을 체크한다.
function add(x, y) { if (typeof x !== "number" || typeof y !== "number") { // 매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생시킨다. throw new TypeError("인수는 모두 숫자 값이어야 합니다."); } return x + y; } console.log(add(2)); // TypeError: 인수는 모두 숫자 값이어야 합니다. console.log(add("a", "b")); // TypeError: 인수는 모두 숫자 값이어야 합니다. -
타입스크립트와 같이 정적 타입을 선언할 수 있는 자바스크립트의 상위 확장을 도입한다.
-
단축 평가를 사용해 매개변수에 기본값을 할당한다.
function add(a, b, c) { a = a || 0; b = b || 0; c = 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 -
매개변수 기본값을 지정한다.
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개 이상을 넘지 않는 것을 권장하고 그 이상의 매개변수가 필요하다면 객체 형식으로 전달하는 것이 유리하다.
하지만 함수 외부에서 함수 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부의 객체가 변경되는 부수효과가 발생하여 주의해야한다.
4. 반환문
return 키워드와 표현식(반환값)으로 이루어진 반환문을 사용해 실행 결과를 함수 외부로 반환한다.
- return 키워드가 반환한 표현식의 평가 결과가 반환된다.
- return 키워드 뒤에 표현식을 지정하지 않으면 undefined가 반환된다.
- 반환문을 생략해도 암묵적으로 undefined가 반환된다.
- 반환문은 함수 몸체 내부에서만 사용할 수 있다.
참조에 의한 전달과 외부 상태 변경
- 값에 의한 전달
- 원시 타입의 인수는 값 자체가 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 그 값을 변경해도 원본이 훼손되지 않는다.
- 참조에 의한 전달
- 객체 타입의 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.
- 객체를 불변 객체로 만들어 사용하면 문제를 해결할 수 있다.
- 객체 타입의 인수는 참조 값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조 값을 통해 객체를 변경할 경우 원본이 훼손된다.
// 매개변수 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"}다양한 함수의 형태
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 () { // SyntaxError: Function statements require a function name
// ...
}();함수 선언문의 형식에 맞지 않아 발생하는 에러
function foo() {
// ...
}(); // SyntaxError: Unexpected token ')'function foo() {}(); // => function foo() {};();세미콜론 자동 삽입에 의해 발생하는 에러
(); // SyntaxError: Unexpected token ')'그룹 연산자는 피연산자가 필수이다.
기명 함수 또는 무명 함수를 그룹 연산자로 감싸면 함수 리터럴로 평가되어 함수 객체가 된다.
즉, 그룹 연산자로 함수를 묶는 이유는 먼저 함수 리터럴을 평가해서 함수 객체를 생성하기 위함이다.
2. 재귀 함수
자기 자신을 호출하는 함수
재귀 함수는 자신을 무한 재귀 호출하므로 탈출 조건을 반드시 만들어야한다.
탈출 조건이 없으면 스택 오버플로 에러를 발생시킬 수 있다.
3. 중첩 함수
함수 내부에 정의된 함수
일반적으로 외부 함수를 돕는 헬퍼 함수의 역할을 한다.
ES6부터는 if 문이나 for 문 등의 코드 블록 내에서도 정의할 수 있다. (권장하지 않음)
4. 콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수라고 한다.
고차함수는 콜백 함수를 자신의 일부분으로 합성한다.
공통 로직은 미리 정의해두고 경우에 따라 변경되는 로직은 추상화하여 콜백 함수를 함수 외부에서 함수 내부로 전달하는 방식으로 사용한다.
콜백 함수가 고차 함수 내부에만 호출된다면 콜백 함수를 익명 함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성한다.
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3그러나 콜백 함수를 전달받는 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 정의한 후 함수 참조를 고차 함수에 전달하는 편이 효율적이다.
// logOdds 함수는 단 한 번만 생성된다.
var logOdds = function (i) {
if (i % 2) console.log(i);
};
// 고차 함수에 함수 참조를 전달한다.
repeat(5, logOdds); // 1 3콜백 함수는 함수형 프로그래밍 패러다임, 비동기 처리, 배열 고차 함수에서 주로 사용된다.
5. 순수 함수와 비순수 함수
-
순수 함수: 외부 상태에 의존하지도 않고 변경하지도 않는 부수 효과가 없는 함수
- 인수의 불변성을 유지한다.
- 함수의 외부 상태를 변경하지 않는다.
var count = 0; // 현재 카운트를 나타내는 상태 // 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다. function increase(n) { return ++n; } // 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경 count = increase(count); console.log(count); // 1 count = increase(count); console.log(count); // 2 -
비순수 함수: 외부 상태에 의존하거나 외부 상태를 변경하는 부수 효과가 있는 함수
var count = 0; // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화한다. // 비순수 함수 function increase() { return ++count; // 외부 상태에 의존하며 외부 상태를 변경한다. } // 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워진다. increase(); console.log(count); // 1 increase(); console.log(count); // 2
함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임이다.