console.log(add(5, 2));
console.log(sub(5, 2));
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
const sub = function (x, y) {
return x - y;
}
함수를 각각 함수 선언문, 함수 표현식으로 나타내고 있다.
함수가 선언되기 전에 각 함수들을 호출하고 있다.
결과는 어떻게 될까?
1️⃣ 함수
함수는 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.
이때, 함수 내부로 입력을 전달받는 변수를 매개변수라 부르고 입력과 출력은 각각 인수, 반환값이라 부른다.
함수를 사용하는 이유
- 유지보수의 편의성
- 코드의 신뢰성
- 코드의 가독성
2️⃣ 함수 정의
함수를 정의하는 방법은 4가지가 있다.
함수 선언문
function add(x, y) {
return x + y;
}
함수의 이름은 생략할 수 있다.
함수 선언문은 표현식이 아닌 문이다.
따라서, 개발자 도구의 콘솔에서 함수 선언문을 실행하면 undefined
가 출력된다.
(만약 함수 선언문이 표현식인 문이라면 표현식이 평가되어 생성된 함수가 출력되어야 한다)
표현식이 아닌 문은 변수에 할당할 수 없기 때문에 변수에 할당할 수 없다.
var add = function add(x, y) {
return x + y;
}
console.log(add(2, 3)); // 5
하지만 위의 코드는 정상적으로 작동한다.
JS 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문이다.
예를 들어 {}
는 블록문 또는 객체 리터럴이 될 수 있다.
{}
이 단독으로 존재 ⇒ 블록문으로 해석{}
이 값으로 표현되어야 할 문맥에서 피연산자로 사용 ⇒ 객체 리터럴로 해석
기명 함수 리터럴도 중의적인 코드이다.
- 함수 이름이 있는 함수 리터럴을 단독으로 사용 ⇒ 함수 선언문으로 해석
- 함수 리터럴이 값으로 평가되어야 하는 문맥 ⇒ 함수 리터럴 표현식으로 해석
JS 엔진은 함수 선언문을 해석해 함수 객체를 생성한다.
이때 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름과는 별도로 생성된 함수 객체를 가리키는 식별자가 필요하다.
JS 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 함수 객체를 할당한다.
따라서 함수를 호출하는 것은 함수 이름으로 하는 것이 아니라 함수 객체를 가리키는 식별자로 호출하는 것이다.
함수 표현식
var add = function (x, y) {
return x + y;
}
JS의 함수는 일급 객체이다.
일급 객체란 값의 성질을 갖는 객체를 의미한다.
JS의 함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다.
함수 리터럴의 함수 이름은 생략할 수 있다. ⇒ 익명 함수
함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.
함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야 한다.
함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름으로 함수를 호출할 수 없다.
var add = function foo(x, y) {
return x + y;
}
console.log(add(2, 3)); // 5
console.log(foo(2, 3)); // ReferenceError
Function 생성자 함수
var add = new Function('x', 'y', 'return x + y');
Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지도 않다.
클로저를 생성하지 않는 등, 함수 선언문이나 표현식으로 생성한 함수와 다르게 동작한다.
화살표 함수
var add = (x, y) => x + y;
화살표 함수는 항상 익명 함수로 정의한다.
기존의 함수 선언문 또는 함수 표현식을 완전히 대체하기 위해 디자인된 것이 아니다.
따라서, 기존의 함수보다 표현만 간략한 것이 아니라 내부 동작 또한 간략화되어 있다.
화살표 함수의 특징은 다음과 같다.
- 생성자 함수로 사용할 수 없다
- this 바인딩 방식이 다르다
- prototype 프로퍼티가 없다
- argument 객체를 생성하지 않는다
3️⃣ 함수 생성 시점과 함수 호이스팅
함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점은 다르다.
함수 선언문도 다른 선언문과 마찬가지로 런타임 이전에 JS 엔진에 의해 먼저 실행된다.
이렇게 함수의 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 JS 고유의 특징을 함수 호이스팅이라 한다.
함수 호이스팅과 변수 호이스팅에는 차이가 있다.
var
키워드로 선언된 변수는 undefined
로 초기화되고, 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화된다.
따라서, var
키워드를 사용한 변수 선언문 이전에 변수를 참조하면 변수 호이스팅에 의해 undefined
가 되지만 함수 선언문으로 정의한 함수를 함수 선언문 이전에 호출하면 함수 호이스팅에 의해 호출이 가능하다.
변수 할당문의 값은 할당문이 실행되는 시점, 즉 런타임에 평가되므로 함수표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.
이러한 함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 규칙을 무시한다.
따라서, 함수 선언문 대신 함수 표현식을 사용할 것이 권장된다.
4️⃣ 매개변수와 인수
함수를 실행하기 위해 필요한 값을 함수 외부에서 내부로 전달하는 경우 매개변수(parameter; 인자)를 통해 인수(argument)를 전달한다.
- 인수
- 값으로 평가될 수 있는 표현식
- 함수를 호출할 때 지정
- 개수와 타입에 제한이 없음
- 매개변수(인자)
- 함수를 정의할 때 선언
- 함수 몸체 내부에서만 참조할 수 있다 (외부에서 참조 불가)
함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다.
개수가 일치하지 않더라도 에러가 발생하지 않으며 인수가 부족하여 할당되지 않은 매개변수는 undefined
, 매개변수보다 인수가 더 많아 초과된 인수는 arguments 객체의 프로퍼티로 보관된다.
중요한 것은 매개변수의 순서에 의미가 있다는 것이다.
따라서, 매개변수가 많아지면 함수를 호출할 때 전달해야 할 인수의 순서를 고려해야 한다.
이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다.
그렇게 때문에 매개변수는 최대 3개를 넘지 않는 것이 권장된다.
💡 결론
console.log(add(5, 2)); // 7
console.log(sub(5, 2)); // undefined
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
const sub = function (x, y) {
return x - y;
}
결론적으로 각 함수의 결과는 위와 같다.
함수 선언문을 통해 함수를 정의하게 되면 함수는 호이스팅에 의해 호출이 가능하기 때문이다.
반대로 함수 표현식을 통해 함수를 정의하는 경우 해당 표현식이 변수에 할당되어야 한다.
변수 할당문의 값은 할당문이 실행되는 시점, 즉 런타임에 평가되므로 함수표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
따라서 함수를 할당하기 전에 호출했기 때문에 undefined
가 된다.
'개발 > JavaScript' 카테고리의 다른 글
[NodeJS] 싱글 스레드와 이벤트 루프 (0) | 2023.07.29 |
---|---|
[JavaScript] 싱글 스레드와 비동기 (0) | 2023.07.23 |
[JavaScript] 원시 값과 객체 (0) | 2023.01.21 |
[JavaScript] 객체 (0) | 2023.01.07 |
[JavaScript] 변수 (0) | 2022.12.31 |