함수란?
함수는 자바스크립트에서 가장 중요한 핵심 개념입니다. 또 다른 자바스크립트의 핵심 개념인 스코프, 실행 컨텍스트, 클로저 생성자 함수에 의한 객체 생성, 메서드, this, 프로토 타입, 모듈화 등이 모두 함수와 깊은 관련이 있습니다. 따라서 함수는 자바스크립트를 정확히 이해하고 사용하기 위해 피해갈 수 없는 핵심중의 하나입니다.
수학의 함수인 f(x,y) = x + y 를 자바스크립트의 함수로 표현해봅시다.
프로그래밍 언어의 함수는 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것입니다.
프로그래밍 언어의 함수도 입력을 받아서 출력을 내보냅니다. 이 때 함수 내부로 입력을 전달받는 변수를 매개 변수 (parameter), 입력을 인수(argument), 출력을 반환값 (return value)이라고 합니다. 또한 함수는 값이며 여러 개 존재할 수 있으므로 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용할 수 있습니다.
함수는 함수 정의 (function definition)를 통해 생성됩니다. 자바스크립트의 함수는 다양한 방법으로 정의할 수 있습니다. 다음은 함수 선언문을 통해 함수를 정의한 예입니다.
함수 정의만으로 함수가 실행되는 것은 아닙니다. 수학의 함수처럼 미리 정의된 일련의 과정을 실행하기 위해 필요한 입력, 즉 인수(argument)를 매개변수를 통해 함수에 전달하면서 함수의 실행을 명시적으로 지시해야 합니다. 이를 함수 호출(function call/invoke)이라 합니다. 함수를 호출하면 코드 블록에 담긴 문들이 일괄적으로 실행되고, 실행 결과 , 즉 반환값을 반환합니다.
함수를 사용하는 이유
함수를 사용함으로써 실행시점을 개발자가 결정할 수 있고 몇 번이든 재사용이 가능합니다. 동일한 작업을 반복적으로 수행해야 한다면 같은 코드를 중복해서 여러 번 작성하는 것이 아니라 미리 정의된 함수를 재사용하는 것이 효율적입니다. 함수는 몇 번이든 호출할 수 있으므로 코드의 재사용이라는 측면에서 매우 유용합니다.
함수를 사용하지 않고 같은 코드를 중복해서 여러 번 작성하면 그 코드를 수정해야 할 때 중복된 횟수만큼 코드를 수정해야 합니다. 따라서 중복된 횟수에 비례해서 코드 수정에 걸리는 시간이 증가합니다. 또한 사람은 실수하기 마련이므로 실수할 가능성 역시 커집니다. 코드의 중복을 억제하고 재사용성을 높이는 함수는 유지보수의 편의성을 높이고 실수를 줄여 코드의 신뢰성을 높이는 효과가 있습니다.
함수는 객체 타입의 값입니다. 따라서 이름(식별자)을 붙일 수 있습니다. 함수 이름은 변수이름과 마찬가지로 함수 자신의 역할을 잘 설명해야 합니다. 적절한 함수 이름은 함수 내부의 코드를 이해하지 않고도 함수의 역할을 파악할 수 있게 돕습니다. 이는 코드의 가독성을 향상시킵니다.
코드는 동작하는 것만이 존재 목적은 아닙니다. 코드는 개발자를 위한 문서이기도 합니다. 따라서 사람이 이해할 수 있는 코드, 즉 가독성이 좋은 코드가 좋은 코드입니다.
함수 리터럴
자바스크립트의 함수는 객체 타입의 값입니다. 따라서 숫자 값을 숫자 리터럴로 생성하고 객체를 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있습니다. 함수 리터럴은 function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성됩니다.
• 함수 이름
- 함수 이름은 식별자입니다. 따라서 식별자 네이밍 규칙을 준수해야 합니다.
- 함수 이름은 몸체내에서만 참조할 수 있는 식별자입니다.
- 함수 이름은 생략할 수 있습니다. 이름이 있는 함수를 기명함수 (named function), 이름이 없는 함수를 익명함수(annonymous function)이라고 합니다.
• 매개변수 목록
- 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분합니다.
- 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당됩니다. 즉, 매개변수의 목록은 순서에 의미가 있습니다.
- 매개변수는 함수 몸체 내에서 변수와 동일하게 취급됩니다. 따라서 매개변수도 변수와 마찬가지로 식별자 네이밍 규칙을 준수해야 합니다.
• 함수 몸체
- 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행단위로 정의한 코드 블록입니다.
- 함수 몸체는 함수 호출에 의해 실행됩니다.
위 예제를 보면 함수 리터럴을 변수에 할당하고 있습니다. "리터럴"에서 살펴보았듯이 리터럴은 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식을 말합니다. 즉, 리터럴은 값을 생성하기 위한 표기법입니다. 따라서 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체입니다. 즉, 함수는 객체입니다.
함수가 객체라는 사실은 다른 프로그래밍 언어와 구별되는 자바스크립트의 중요한 특징입니다. 이 특징을 제대로 이해하지 않으면 함수를 제대로 이해하기 어렵습니다.
함수 정의
함수 정의란 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들 그리고 반환할 값을 지정하는 것을 말합니다. 정의된 함수는 자바스크립트 엔진에 의해 평가되어 함수 객체가 됩니다. 함수를 정의하는 방법에는 4가지 방법이 있습니다.
함수 선언문
함수 표현식
Function 생성자 함수
화살표 함수 (ES6)
모든 함수 정의 방식은 함수를 정의한다는 면에서 동일합니다. 단, 미묘하지만 중요한 차이가 있습니다.
함수 선언문
함수 선언문을 사용해 함수를 정의하는 방식은 다음과 같습니다.
함수 선언문은 함수 리터럴과 형태가 동일합니다. 단, 함수 리터럴은 함수 이름을 생략할 수 있으나 함수 선언문은 함수 이름을 생략할 수 없습니다.
함수 선언문은 표현식이 아닌 문입니다. 크롬 개발자 도구의 콘솔에서 함수 선언문을 실행하면 완료값 undefined가 출력됩니다. 함수 선언문이 만약 표현식인 문이라면 완료 값 undefined 대신 표현식이 평가되어 생성된 함수가 출력되어야 합니다.
"표현식인 문과 표현식이 아닌 문" 에서 살펴보았듯이 표현식이 아닌 문은 변수에 할당할 수 없습니다. 함수 선언문도 표현식이 아닌 문이므로 변수에 할당할 수 없습니다. 하지만 다음 예제를 실행해보면 함수 선언문이 변수에 할당되는 것처럼 보입니다.
이렇게 동작하는 이유는 자바스크립트 엔진이 코드 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문입니다. 함수 선언문은 함수 이름을 생략할 수 없다는 점을 제외하면 함수 리터럴과 형태가 동일합니다. 이는 함수 이름이 있는 가명 함수 리터럴은 함수 선언문 또는 함수 리터럴 표현식으로 해석될 가능성이 있다는 의미입니다.
예를 들어, { }은 블록문일 수도 있고 객체 리터럴일 수도 있습니다. 즉, { }은 중의적 표현입니다. 자바스크립트 엔진은 { }을 코드 블록으로 해석할까 객체 리터럴로 해석할까? { } 처럼 중의적인 코드는 코드 문맥에 따라 해석이 달라집니다. { } 이 단독으로 존재하면 자바스크립트 엔진은 { }을 블록문으로 해석합니다. 하지만 { }이 값으로 평가되어야 할 문맥에서 피연산자로 사용되면 자바스크립트 엔진은 { } 을 객체 리터럴로 해석합니다. 이처럼 동일한 코드도 코드의 문맥에 따라 해석이 달라질 수 있습니다.
기명 함수 리터럴도 중의적인 코드입니다. 따라서 코드의 문맥에 따라 해석이 달라질 수 있습니다. 자바스크립트 엔진은 함수 이름이 있는 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석하고 함수 리터럴 값으로 평가되어야 하는 문맥, 예를 들어 함수 리터럴을 변수에 할당하거나 피연산자로 함수 리터럴 표현식으로 해석합니다. 이 때 함수 선언문이든 함수 리터럴 표현식이든 함수가 생성되는 것은 동일합니다. 하지만 함수를 생성하는 내부 동작에 차이가 있습니다.
위 예제에서 단독으로 사용된 함수 리터럴(foo)은 함수 선언문으로 해석됩니다. 하지만 그룹 연산자()내에 있는 함수 리터럴(bar)은 함수 선언문으로 해석되지 않고 함수 리터럴 표현식으로 해석됩니다. 그룹 연산자의 피연산자는 값으로 평가될 수 있는 표현식이어야 합니다. 따라서 표현식이 아닌 문인 함수 선언문은 피연산자로 사용할 수 없습니다.
이처럼 이름이 있는 기명 함수 리터럴은 코드의 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식으로 해석됩니다. 함수 선언문과 함수 리터럴 표현식은 함수 객체를 생성한다는 점에서 동일하지만 호출에 차이가 있습니다. 위 예제에서 함수 선언문으로 생성된 foo는 호출할 수 있으나 함수 리터럴 표현식으로 생성된 bar는 호출할 수 없습니다.
앞에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자라고 했습니다. 이는 함수 몸체 외부에서는 함수이름으로 함수를 참조 할 수 없으므로 함수 몸체 외부에서는 함수 이름으로 함수를 호출할 수 없다는 의미입니다. 즉, 함수를 가리키는 식별자가 없다는 것과 마찬가지 입니다. 따라서 위 예제의 bar함수는 호출 할 수 없습니다.
하지만 위 예제어서 함수 선언문으로 정의된 함수는 foo라는 이름으로 호출할 수 있었습니다. foo는 함수 몸체 내부에서만 유요한 식별자인 함수 이름이므로 foo로 함수를 호출할 수 없어야 합니다. foo라는 이름으로 함수를 호출하려면 foo는 함수 이름이 아니라 함수 객체를 가리키는 식별자여야 합니다. 그런데 위 예제에서는 식별자 foo를 선언한적도 없고 할당한 적도 없습니다. foo는 도대체 무엇일까요? 결론부터 말하자면 foo는 자바스크립트 엔진이 암묵적으로 생성한 식별자입니다.
자바스크립트 엔진은 함수 선언문을 해석해 함수 객체를 생성합니다. 이때 함수 이름은 함수 몸체 내부에서만 유요한 식별자이므로 함수 이름과는 별도로 생성된 함수 객체를 가리키는 식별자가 필요합니다. 함수 객체를 가리키는 식별자가 없으면 함수 객체를 참조할 수 없으므로 호출할 수도 없습니다. 따라서 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당합니다. 지금까지 살펴본 함수 선언문을 의사 코드로 표현하면 다음과 같습니다.
함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출합니다. 즉, 함수 선언문으로 생성한 함수를 호출한 것은 함수 이름 add가 아니라 자바스크립트 엔진이 암묵적으로 생성한 식별자 add인 것입니다. 함수 이름과 변수 이름이 일치하므로 함수 이름으로 호출되는 듯하지만 사실은 식별자로 호출된 것입니다.
사실은 위 의사 코드가 바로 다음에 살펴볼 함수 표현식입니다. 결론적으로 자바스크립트 엔진은 함수 선언문을 함수 표현식으로 변환해 함수 객체를 생성한다고 생각할 수 있습니다. 단, 함수 선언문과 함수 표현식이 정확히 동일하게 동작하는 것은 아닙니다.