[javascript] var 함수명 = function(){} 과 function 함수명(){}의 차이와 호이스팅(Hoisting)의 이해

함수를 정의하는 방법은 2가지가 있습니다.

2가지 모두 표준 자바스크립트 함수 정의 방법이기 때문에 어느 쪽을 사용해도 됩니다.

함수는 다음과 같이 2가지 방법으로 정의할 수 있습니다.

편의상 function1 선언은 "변수 함수 선언", function2는 "function 함수 선언"이라고 합니다.

var function1 = function() {
	console.log("Hello!");
};

function function2() {
    console.log("Hello!");
}

함수를 호출해서 사용하는 방법도 동일하지만, 두가지 방법은 큰 차이점이 있습니다.

다음의 자바스크립트 코드에서 "function2();"는 정상 실행되지만 "function1();"은 함수가 아니라는 에러가 발생합니다.

function1(); /* function1 호출 */
var function1 = function() {
    console.log("Hello!");
};

function2(); /* function2 호출 */
function function2() {
    console.log("Hello!");
}

변수 함수 선언을 위한 변수 선언자를 "var"로 선언한 것에 주의해야 합니다.

"let"을 사용해도 문제가 없지만, "let"은 스코프 범위를 가지며, 스코프 범위에 따라 에러가 발생하기도 합니다.

스코프 문제에  대해서는 뒤에서 자세히 설명하므로 여기서는 일단 "var"로 선언된 것만 확인하고 넘어가면 됩니다.

호이스팅(Hoisting)

왜 이런가에 대해서 알기 전에 호이스팅(Hoisting)이라는 자바스크립트의 코드 처리 방식을 이해해야 합니다.

자바스크립트는 인터프리터 방식의 언어이기 때문에 코드의 위에서부터 아래로 한 줄씩 순차적으로 실행됩니다.

순차적으로 실행된다는 것은 현재 실행되는 코드의 밑에 있는 내용을 모른다는 것입니다.

따라서 코드의 하단에 함수 정의가 있을 경우 위쪽 코드에서 함수를 호출하는 것은 불가능합니다. 당연히 에러가 발생합니다.

이런 문제를 피하기 위해 자바스크립트는 전처리 과정을 통해 전체 코드의 변수와 함수 정의를 코드 맨 위로 끌어올려서 선언해주는 작업을 먼저 한 후 코드를 순차적으로 실행됩니다.

변수와 함수 정의를 맨 위로 끌어올리는 과정을 호이스팅(Hoisting) 이라고 합니다.

호이스팅으로 인해 자바스크립트는 변수나 함수가 정의된 위치에 상관없이 변수나 함수를 어느 위치에서나 사용할 수 있게 됩니다.

변수 선언 방식의 함수는 변수만 호이스팅이 되기 때문에 하프 호이스팅(Half Hoisting) 이라고 따로 구분하기도 합니다.

변수로 선언하는 함수의 실행 방식

변수로 선언하는 함수는 함수 객체를 변수에 대입하기 때문에 호이스팅이 변수명으로 처리됩니다.

호이스팅이 일어나면서 변수명은 자바스크립트 최상단으로 끌어올려지지만, 실제 함수의 할당은 원래 함수를 선언한 위치에 도달해야 합니다.

따라서 "function1();"호출은 함수가 아니라는 에러를 표시하게 됩니다.

"function"으로 시작하는 함수 선언과 변수명으로 시작하는 함수 선언은 다음과 같은 차이점이 있습니다.

따라서 변수에 선언한 함수는 반드시 함수 선언 뒤에 호출해야 합니다.

엄격 모드('use strict')에서의 호이스팅과 스코프

일반적인 환경에서는 앞서의 설명처럼 변수 선언 함수를 함수 선언 앞에서 호출하면 에러가 발생합니다.

당연하지만 함수 선언 뒤에서 호출하면 두 가지 함수 선언 모두 잘 동작합니다.

그러나 엄격 모드에서는 경우에 따라 function 함수 선언과 변수 함수 선언이 다르게 호이스팅될 수도 있습니다.

그리고 자바스크립트의 변수 선언자인 "var"와 "let"에 따라서 스코프가 미치는 범위에 따라 호이스팅이 다르게 되면서 에러가 발생할 수도 있습니다.

다음 코드는 변수 선언 함수와 function 선언 함수를 함수 선언 뒤에서 호출하는 예입니다.

함수 선언을 코드 블록으로 감싼 것과 "function1" 변수 선언 함수의 변수 선언자가 "var"라는 것을 특히 주의해야 합니다.

이 코드는 "function1", "function2"가 모두 잘 실행됩니다.

{
    var function1 = function(){
        console.log('function1');
    };
    function function2(){
        console.log('function2');
    };
}
function1();
function2();

"var"를 "let"으로 다음과 같이 바꿉니다. 에러가 발생합니다.

{
    let function1 = function(){
        console.log('function1');
    };
    function function2(){
        console.log('function2');
    };
}
function1();
function2();

2개의 함수 선언을 코드 블록 "{}"으로 감싼 것이 특히 중요합니다. 코드 블록으로 감싸지 않으면 에러 없이 잘 실행됩니다. 변수 선언과 호이스팅이 자바스크립트 전역으로 처리되기 때문에 스코프 문제가 발생하지 않으며, 함수 호출도 함수 선언 뒤에서 이루어지기 때문에 문제가 없습니다. 코드 블록({})으로 감싸지 않았을 때는 "let"은 "var"와 같습니다.

"let"으로 변수 선언자를 바꾸면 "function1" 변수는 코드 블록 로컬로 스코프가 적용됩니다.

호이스팅 또한 코드 블록 로컬로 처리됩니다. 함수 호출이 함수 선언 뒤에서 있으므로 문제가 될 것은 없지만, 호이스팅 된 변수 "function1"이 코드 블록 로컬 변수이기 때문에 코드 블록이 끝나는 시점에 "function1" 변수는 사라집니다.

그래서 "function1" 함수 호출은 정의되지 않았다는 오류가 발생합니다.

이 코드에 엄격 모드를 추가로 적용합니다. "function2" 함수를 호출하는 행에서도 에러가 발생합니다.

자바스크립트의 코드 실행 중 에러가 발생하면 첫 번째 에러가 발생한 행에서 멈추기 때문에 "function2"의 에러가 표시되지 않지만, "function1" 함수 호출 행을 삭제하면 "function2"가 바로 에러가 발생하는 게 확인됩니다.

'use strict';
{
    let function1 = function(){
        console.log('function1');
    };
    function function2(){
        console.log('function2');
    };
}
function1();
function2();

엄격 모드에서는 function 함수 선언도 스코프가 적용됩니다. 따라서 코드 블록이 끝나는 시점에 두 가지 함수 선언은 모두 소멸하고 두 개의 함수 호출은 모두 정의되지 않은 함수를 호출하게 됩니다.

엄격 모드 상태에서 "let"으로 선언한 변수 선언자를 "var"로 다시 바꾸면 "function1" 호출은 정상적으로 됩니다. "var" 변수는 코드 전역으로 변수가 처리되기 때문에 코드 블록이나 엄격 모드에 영향을 받지 않습니다.