본문 바로가기

개발공부/Javascript

[JS] 호이스팅

호이스팅

자바스크립트 엔진이 실행 컨텍스트를 생성할 때 변수나 함수 선언을 먼저 읽고 처리하는 특성, 물리적으로 끌어 올려지는 것은 아님. 끌어 올려지는 것처럼 작동

 

코드 실행 전에 실행 컨텍스트 구성 과정에서 호이스팅이 발생한다. 변수나 함수의 값이 선언되기 이전에 참조하면 예기치 않는 결과나 오류가 발생할 수 있다.같은 이름의 함수를 재선언하면 이전 함수를 덮어쓰는 문제가 발생할 수 있다. 이를 방지하기 위해 호이스팅 매커니즘을 이해하여야 안정적인 코드 작성이 가능하다.

함수 선언문과 함수 표현식의 호이스팅 처리 방식이 다르다. 함수 선언문은 전체 함수 구조가 호이스팅, 함수 표현식은 변수 선언 부분만 호이스팅 된다. 함수 본문이 호이스팅 되지는 않는다. 함수 표현식을 사용할 때는 해당 함수가 실행 컨텍스트에 등록되기 전까지는 호출할 수 없다는 점을 유념하여야 한다. 코드 실행 순서와 안전성에 직접적인 영향을 미친다.

var 키워드 사용할 때는 호이스팅 특성이 나타나지만, let, const를 사용할 때에는 변수 선언 전에 해당 변수에 접근할 수 없는 temporary dead zone 이 생긴다.

 

hoisiting: The behaviour of JS syntax which allows variables to be used. (자바스크립트에서는 변수나 함수를 선언 전에 사용할 수 있다. ) 자바스크립트 엔진은 코드를 실행하기 전에 실행 컨텍스트를 생성한다.이 과정에서 변수와 함수 선언을 먼저 읽고 처리한다. 변수와 함수가 선언된 위치와 상관 없이 스코프의 최상단으로 끌어올려지는 것처럼 동작하는 특징이 호이스팅이다. js 에서 발생하는 특정 동작을 설명하기 위해 만들어진 용어이다.

 

자바스크립트에서는 변수나 함수를 선언 전에 사용할 수 있다. 마치, 변수와 함수 선언이 그들이 속한 범위의 최상단으로 끌어 올려진 것처럼!

 

실행 컨텍스트가 동작하는 과정에서 해당 선언들이 scope의 최상단에서 처리되는 것처럼 보이는 현상이기 때문이다.

Q. const나 let은 호이스팅이 되나요? 이들이 호이스팅의 영향을 받나요?

A. const와 let은 hoisting의 일부 문제점을 해결해주는 식별자이다. 자바스크립트 엔진 레벨에서 var 키워드와 어떻게 다르게 동작하는지 설명하는 것이 더 중요하다. const와 let이 일시적인 사각지대인 temporary dead zone 에 속해 있어 호이스팅을 방지하는 메커니즘에 대한 깊이 있는 설명을 할 수 있어야 한다.

 

console.log(hello);
var hello;
var hello1;
console.log(hello1);

 

hello is not defined가 출력 될 것으로 예상했다. 코드를 실행해보면 undefined가 출력 된다. 변수의 선언이 코드 내 실제 위치와 무관하게 (위 두 코드의 결과가 동일) 해당 범위의 최상단으로 이동하는 것처럼 보인다. 이런 동작 방식 때문에, 호이스팅이라는 이름이 붙여진 것이다. 이런 특성은 변수 선언 뿐 아니라 함수 선언에서도 마찬가지 현상이다

console.log(test);
function test() {}

 

변수는 식별자만 우선 호이스팅 되는 특성이 있어 초기화 이전에 변수를 호출하면 undefined가 출력되는 것을 확인할 수 있다. 함수는 식별자 뿐만 아니라 함수의 전체 정의 즉, 함수 본문도 포함이 된다. 함수 같은 경우에는 실제 선언 위치 이전에 호출을 할 수 있다. 함수 선언문은 해당 함수가 포함된 실행 컨텍스트가 활성화 될 때 이미 사용 가능한 상태가 된다.

console.log(sum(1, 2)); // 3
function sum (a, b) {
    return a + b;
}

 

함수 선언 이전에 console.log() 사용하여 3이 출력 되었다. js의 함수 호이스팅 덕에 sum 함수의 전체 정의가 호이스팅 되어서 console.log()실행되기 이전에 이미 사용할 준비가 되어 있고, 따라서 함수 선언 이전에 이 함수를 호출해도 sum 함수가 정상 작동하여 결과 반환한다.

 

호이스팅 작동원리 - 실행 컨텍스트

호이스팅의 작동 원리는 실행 컨텍스트 때문이다.실행컨텍스트는 엔진이 실행 가능한 코드를 구조화하고 분류하는 데 사용하는 추상적 개념으로 코드의 실행 단계에서 매우 중요한 역할을 담당한다. 엔진이 코드를 실행할 때 실행 컨텍스트는 활성화가 되고 이 시점에서 변수와 함수 선언의 호이스팅, this의 결정이 이루어진다.

GlobalExecutionContext = {
    VariableEnvironment: {
        hello: undefined,
        test: <ref to test function></ref>
    }
 }

 

js 코드가 실행될 때 , js 파서가 활동을 시작하고 이 과정에서 전역 컨텍스트가 초기화 된다. 전역 실행 컨텍스트는 VariableEnvironment라는 변수 객체를 생성한다. 프로그램에 의해 선언된 모든 함수와 변수의 식별자 정보를 포함한다. 이 때, 변수는 초기에 undefined로 할당하고 함수는 그 자체의 함수 객체로서 할당을한다. 이런 초기화 메커니즘에서 js 엔진이 실행 컨텍스트 내에서 식별자들을 변수 객체의 프로퍼티로 미리 할당을 하고 그 값을 미리 undefined로 설정해둔다. 이러한 이유로 선언이 코드 상의 후반부에 이루어져도 해당 함수나 변수를 코드 상의 앞 부분에서 사용할 수 있게 된다.

 

호이스팅의 장점과 단점

장점은 유연하고 동적인 언어의 특징을 반영한다. 선언하기 이전에 호출할 수 있어서 조건문이나 반복문에 있지 않는 경우라면 코드의 순서를 자유롭게 개발자가 조정할 수 있다. 함수 선언을 파일이나 코드 블록의 상단에 배치할 수 있어서 코드의 구조를 명확하게 하고 가독성을 향상 시킬 수 있다.

단점은 버그의 원인이 될 수 있다. 함수를 선언하기 전에 호출할 수 있어서, 이런 점은 코드의 유연성을 증가하는 장점도 있지만, 코드의 전체적인 흐름을 이해하기 어렵게 만들 수 있다. 여러 개발자가 작업하는 환경에서 이미 선언된 함수를 나중에 동일한 이름으로 선언하면 덮어쓰게 되고 버그가 생길 수 있다. var로 선언된 변수는 호이스팅이 될 때 초기화 되지 않은 상태 즉, undefined가 할당이 된다. 코드의 순서 상 변수가 선언되기 전에 참조 되면 예상한 값 대신 undefined가 반환이 되어 코드의 흐름을 따라 가기 어렵게 한다.

function process() {
    return '처리중...';
}
console.log(process()); //새로운 처리..

function process() {
    return '새로운 처리..'
}

 

새로운 처리.. 가 출력이 된다. 코드의 동작은 위에서 아래로 동기적으로 작동하므로 처리중…이 출력될 것이라고 예상하였지만, 새로운 처리.. 가 출력이 된다. 이와 같은 현상이 나타나는 이유는 호이스팅에 의해 콘솔이 실행되기 이전에 2번째 함수가 1번째 함수를 덮어 쓰게 된다. 즉, 위에서 선언한 함수명을 다시 선언하게 되면, 기존 함수의 동작이 예상치 못하게 변경될 수 있다. ⇒ 버그로 이어질 수 있다.

 

호이스팅 대처법

변수 키워드인 let, const와 함수 표현식을 사용하는 방법이 있다. ⇒ 호이스팅이 되지 않는 것은 아니다. 각각의 동작 방식과 특성에 의해 호이스팅으로 인한 버그를 방지할 수 있다.

 

let, const로 선언된 변수

let, const로 선언된 변수는 호이스팅은 되는데, 임시적 사각지대 (Temporal Dead Zone) 의 특성을 이용해서 선언 전에 접근하려고 하면 참조 에러를 발생 시킨다. var는 호이스팅이 되어서 선언된 위치와 상관없이 초기에 undefined를 할당하여 이러한 특성 때문에 변수가 선언되기 이전에 사용할 수 있다. let, const는 호이스팅은 되지만 undefined를 할당하지 않는다. 호이스팅은 되지만 값이 없기 때문에, 선언 전에는 접근할 수 없게 된다. 임시적 사각지대 (Temporal Dead Zone) - 어느 스코프에도 속하지 않는다.

console.log(a); // undefined
var a; 

console.log(b); //Cannot access 'b' before initialization
let b;

 

var 로 선언된 변수는 호이스팅 되면서 undefined 값을 할당하여 초기화가 된다. 하지만, let const는 선언 이전에 해당 변수를 아예 참조하지 못하도록 강제한다. 이를 통해 변수를 선언한 후에만 접근 가능하게 하여 개발자가 명확하게 코드 작성 및 관리할 수 있도록 할 수 있다.

 

함수 표현식

호이스팅을 예방할 수 있는 또다른 방법은 함수 표현식이 있다. 변수에 함수를 할당하는 방식이다. 함수를 변수에 할당하여 함수를 값처럼 취급한다. 호이스팅 되는 함수 선언문과는 다르게 작동한다. 함수 표현식을 보면 함수를 변수에 할당하기 때문에 함수 표현식은 변수 호이스팅의 규칙을 따르게 된다. 함수 자체의 본문은 호이스팅 되지 않지만, 함수 선언문은 함수 자체가 호이스팅 되어 함수 선언 이전에 호출이 가능했만, 함수 표현식은 변수만 호이스팅 되기 떄문에 선언된 후에만 해당함수를 호출 할 수 있다.

console.log(test()); // 1
function test() {
    return 1;
}

console.log(test2()); // Cannot access 'test2' before initialization
const test2 = function () {
    return 1;
}

 

함수를 표현식으로 할당하면 변수 영역이 호이스팅 되기 때문에, let const가 호이스팅 되는 것과 동일한 현상을 나타내는 것을 볼 수 있다. 함수 선언문과 함수 표현식은 서로 다른 호이스팅 방식을 가진다. 함수 선언문은 자바스크립트에서 함수를 정의하는 가장 기본적이고 널리 사용되는 방법이다. 이 방식은 function 키워드로 시작되고 이름이 지정된 함수를 정의한다. 함수 선언문이 호이스팅 될 때 함수 전체 구조가 호이스팅 된다. 해당 함수가 스코프 내 어느 곳에서나 호출될 수 있음을 의미한다. 즉 함수의 정의보다 앞서서 함수를 호출하는 것을 가능하게 한다. 유연성이 크지만 버그를 일으킬 수 있다.

함수 표현식은 변수에 함수를 할당하는 방식으로 구현이 된다. 함수 표현식은 익명함수 또는 기명함수를 사용하여 정의할 수 있다. 호이스팅 될 때, 함수 자체가 아닌 변수 선언 만이 호이스팅이 된다. 실제 함수의 할당은 코드가 실행되는 시점에 이루어 진다. 이 함수는 그것이 정의된 코드라인 이후에 접근할 수 있다. 함수의 스코프와 실행 타이밍을 신중히 고려해야 한다.

 

Q. let과 const는 호이스팅이 되지 않나요?

A. 호이스팅이 되지 않는 것처럼 동작한다. 하지만, let과 const도 호이스팅이 된다. 다만, var 키워드와는 다른 방식의 호이스팅 메커니즘을 가진다. 이들도 호이스팅이 되지만, 임시적 사각지대를 뜻하는 Temporal Dead Zone의 보호를 받는다. TDZ란 변수가 선언이 되었지만 초기화 되지 않은 상태를 나타낸다. 이 상태에서 변수에 접근하려 하면 Reference Error가 발생한다. var로 선언된 변수는 호이스팅이 될때 바로 undefined로 초기화가 되어 선언 이전에 변수를 참조하면 undefined라는 값이 반환이 되지만, TDZ 상태로 들어간 변수는 에러를 발생시킨다.

 

 

자바스크립트 엔진 v8에서 var와 let 변수 처리 방법

높은 성능, 효율성, 다양한 운영 체제에서 동작 되어야 함.

var 키워드로 변수 선언 시에 js 엔진에서 어떤 코드를 실행하는지 축약한 버전이다.

var 키워드로 변수를 선언하는 과정에서 js 엔진은 컴파일 타임에 자동 타입 추론 (auto typing)을 수행한다. 이 과정에서 변수 이름을 지정하고 내부적으로 allocateTo 메소드로 해당 변수에 대한 메모리 공간을 할당한다. 이 메모리 할당은 variable location을 통해 위치가 지정되고 여기서 주요한 특징은 var 키워드르 사용할 때 allocateTo 메소드를 통해 메모리에 바로 undefined값이 할당된다는 점이다.

auto var = scope->DeclareParameter(name, VariableMode: kVar, is_optional);
var ->AllocateTo(VariableLocation::PARAMETER, 0);

 

let 키워드로 변수 선언 시에는 절차가 더 복잡하다. js 엔진은 proxy 객체를 생성하여 DeclareBoundVariable 메소드를 통해 변수를 선언한다. 이 변수에 대한 참조를 proxy에 할당한다. 이후에 VariableMode에 kLet을 할당해서 let 키워드로 선언된 변수임을 명시한다. 여기서 가장 중요한 점은 set_initializer_position 메소드 이다. 이 메소드를 통해 초기화 과정이 진행이 된다. var와는 달리 let 키워드 set_initializer_position 과정을 거치게 되어 TDZ에 들어가게 된다. 이는 변수가 선언되어 호이스팅이 되지만 초기화 되지 않은 상태에서는 접근할 수 없음을 의미한다. 변수가 선언되고 초기화된 이후 시점에만 접근이 가능하다. TDZ의 존재는 let const 변수가 더 엄격한 스코프 관리를 가능하게 하고 변수 사용 전에 초기화가 이루어지도록 보장한다. 실행 컨텍스트에 의해 let const 키워드도 호이스팅이 되지만 TDZ에 의해 초기화되지 않은 상태에서는 접근할 수 없다.

VariableProxy* proxy = DeclareBoundVariable(variable_name, VariableMode::kLet
, proxy->var()->set_initializer_position(end_pos));

 

정리

특별한 기능이 아닌 자바스크립트의 특성이다. 호이스팅은 실제로 변수가 끌어올려지는 것은 아니다. js엔진이 코드를 해석하고 실행하는 방식에서 비롯된 결과이다.

js엔진은 코드 실행 전에 실행 컨텍스트를 생성한다. 이 과정에서 모든 변수와 함수 선언을 먼저 읽고 처리한다. 호이스팅은 실제 변수나 함수가 물리적으로 코드 상단으로 이동하는 것이 아닌 js엔진의 동작 방식을 설명하기 위해 만들어진 용어이다. 실제 변수 함수가 이동하는 것이 아니라 변수와 함수가 실행 컨텍스트 내에서 먼저 처리가 된다는 것을 나타낸다.

let, const도 호이스팅이 되지만 TDZ로 참조를 방어할 수 있다.

 

[참고]

패스트캠퍼스 강의 (내 생에 마지막 JavaScript : 기초 문법부터 실무 웹 개발까지 한 번에 끝내기 초격차 패키지 Online) 를 기반으로 작성하였고, 추가 서치 내용을 정리하였습니다.