본문 바로가기

자바스크립트

[JS] var를 지양해야 하는 이유 (feat. 호이스팅, 스코프, 실행 컨텍스트)

⚠️ var 대신 let 또는 const 를 사용하세요.

 

자바스크립트를 처음 공부할 때 많이 듣는 말이다. 그리고 이 이유에 대해서 의구심을 품고 조금이라도 공부해봤다면 아래의 이유로 사용하지 말아야 겠구나 하고 넘어갔을 것이다.

 

1. var는 중복으로 선언이 된다.

2. 스코프의 범위가 모호하다.

3. 호이스팅이 된다.

 

처음에 공부할 때는 지식의 깊이가 너무 얕아서 깊게 파려 하면 오히려 역효과가 날 수 있기 때문에 이 정도만 이해하고 넘어갔었다. 하지만 지금 자바스크립트에 대해서 깊게 공부할수록 꼬리를 물고 연결되는 개념들이 정말 많았는데, 차근차근 정리해보려 한다.

컴파일 언어와 스크립트 언어

우선 컴파일 언어와 스크립트 언어에 대해 알고 넘어가자. 만약 C언어를 배워봤다면, “C는 컴파일 언어이다” 라는 말을 들어봤을 것이다. 컴파일 언어란 우리가 작성한 소스 코드, 즉 사람이 이해하기 쉽게 만든 프로그래밍 언어를 기계가 이해하기 쉽도록 1과 0으로만 구성된 기계어로 변환되어 실행되는 언어를 말하고, 여기서 사람이 이해하기 쉬운 코드(소스 코드)를 기계어로 변환하는 과정이 컴파일링이다.

자바스크립트를 공부한다면 자바스크립트가 스크립트 언어라는 것을 알고 있을 것이다. 그렇다면 컴파일링 과정이 없다는 것인가? 그렇다. 자바스크립트는 컴파일되지 않고 인터프리터가 소스 코드를 한줄 한줄 읽으면서 바로 실행된다. 즉 컴파일 과정이 없고 바로 실행(런타임)된다.  수정) 자바스크립트 엔진 제조사(V8의 경우 구글)에 따라 어느정도 컴파일 과정이 있다.

자바스크립트의 인터프리터, ‘해석’과 ‘평가’

자바스크립트 엔진

자바스크립트에서 자바스크립트 코드를 읽는 인터프리터는 바로 자바스크립트 엔진이다. 자바스크립트 엔진도 많은 버전이 있으며 가장 범용적인 엔진은 구글의 V8이다.

자바스크립트 엔진은 자바스크립트 소스 코드를 한줄 한줄 읽어 바로 실행시키는데, 이때 코드를 한줄 한줄 읽는 것을 ‘해석한다’ 라고 한다.

해석과 평가

‘해석’과 ‘평가’란 무엇일까?

10
>> 10

자바스크립트 엔진은 10을 해석하여 10으로 평가한다.

10 + 20
>> 30

자바스크립트 엔진은 10 + 20을 해석하여 30으로 평가한다.

이처럼 자바스크립트 엔진은 런타임 이전에 코드를 평가하는 과정을 거친다.

변수

프로그래밍 언어에서 변수가 없는 프로그래밍 언어가 있을까? 아마 있을 수도 있지만 제대로 된 프로그래밍 언어가 아닐 것이다. 변수는 선언과 할당을 통해 값을 저장할 수 있다. 그런데 정확히 말하자면 값을 저장하는 것이 아니라 메모리를 기억하는 것이다.

var x = 10;

이런 코드는, x라는 변수를 선언해서 10이라는 값을 할당하면 메모리 공간 어딘가에 10이라는 값이 저장되고, 변수 x는 그 메모리 주소를 기억하고 있는 것이다.

이 글의 요지인 var는 ES6이전까지 변수를 선언할 수 있는 유일한 키워드 였다.

ES6에서letconst가 추가되었고, var는 기존에 var가 가지고 있었던 여러가지 문제점들로 인해 사용하지 않을 것을 권고한다.

🤔 이제 그 문제점을 알아보자.

1. var는 중복 선언이 된다.

var x = 1;
var x = 2;
var x = 3;

console.log(x); // 3

var 키워드를 이용해서 x라는 변수 명을 계속해서 선언해도 문제가 없다. 이런 특징은 언뜻 봐도 문제로 보이는데, 파일의 규모가 커진다면 문제가 있는데도 어떤 문제인지 찾기도 힘들 것이다.

2. var는 스코프의 범위가 모호하다?

❗ 사실 이 말 자체가 굉장히 모호한 말이지만, 처음 내가 이해한 바로는 이렇다.

var i = 10;
for (var i = 0; i < 3; i++) {
  console.log(i); // 0 1 2
}
console.log(i); // 3

이 코드를 보면, i 값이 의도치 않게 변경되었다. 스코프의 범위가 모호해지고 개발에서 어려움을 초래할 수 있다는 것인데, 이제 왜 var는 이런 스코프를 가지는지 알아보겠다.

함수 레벨 스코프

위 예시에서 i의 값이 의도치 않게 변경된 이유는 var 키워드로 선언한 변수의 스코프는 함수를 기준으로 하기 때문이다. 위 코드는 현재 전역이므로 i는 공유된다.

var x = 1;

function foo() {
  var x = 2;
  console.log(x); // 2
}

foo();
console.log(x); // 1

이 코드에서, 전역에서 선언한 x는 그대로 1로 평가되고, foo 함수 안에서 선언한 x는 2로 평가된다. 이렇게 스코프의 범위가 함수 레벨이라면 전역 변수를 남발할 가능성을 높이고 이로 인해 의도치 않게 전역 변수가 중복 선언되는 경우가 발생한다.

블록 레벨 스코프

하지만 let과 const는 블록 레벨 스코프를 따른다.

let i = 10;

for (let i = 0; i < 3; i++){
  console.log(i); // 0 1 2
}

console.log(i); // 10

3. var는 호이스팅된다? (호이스팅 이란)

❗ 정확히는 var 뿐만 아니라 let, const, function, function*, class 키워드를 사용해서 선언하는 모든 식별자는 호이스팅된다.

사실 앞서 자바스크립트는 스크립트 언어이고, 인터프리터(자바스크립트 엔진)에 의해 런타임 전에 평가된다 는 사실을 먼저 짚고 간 이유는 이 호이스팅 때문이다. 호이스팅이 무엇인지 예시를 통해 알아보자.

console.log(x); // undefined

var x = 10;

분명 첫째 줄에서 x는 선언되기 전이지만 에러가 나지 않았다. (undefined는 자바스크립트의 원시 값이다.) 마치 변수가 먼저 끌어올려진 것 같다하여 ‘호이스팅’이라 한다. 에러가 나지 않은 이유는 자바스크립트 엔진이 이미 변수 x에 대해 런타임 전에 평가과정을 거쳤기 때문이다. 이처럼 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라 한다. 변수 선언문 이전에 변수를 참조해도 에러를 출력하지 않는다는 것은 프로그래밍 흐름에 맞지 않고, 가독성을 떨어뜨리기 때문에 개발에 어려움을 주고 예상치 못한 오류를 발생 시킬 수 있다.

실행 컨텍스트

❗ 여기서 호이스팅에 대해 조금 더 이해하려면 실행 컨텍스트를 알고 있어야 한다. 실행 컨텍스트에 대한 내용도 꽤 많고 어렵기 때문에 여기서는 가볍게만 정리하고 넘어가려 한다.

자바스크립트의 모든 코드는 런타임 이전에 평가 과정을 거치면서 실행하기 위한 준비를 한다. 즉, 자바스크립트 엔진은 (1) 소스코드의 평가(평가과정) 와 (2) 소스코드의 실행(런타임) 과정으로 나누어진다.

자바스크립트 엔진이 실행 전, 모든 소스 코드를 평가하는 과정에서 변수, 함수 등의 선언문만 먼저 실행하여 그 식별자를 키로 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다.

 

1. 예를 들어 이런 코드가 있다.

var x = 1;

 

2. 자바스크립트 엔진에 의해 모든 소스 코드는 평가된다.

3. 이 때, x가 실행 컨텍스트의 환경 레코드에 등록된다.

실행컨텍스트의 환경레코드
x : undefined

4. 평가 과정을 거치고 런타임에서, 자바스크립트 엔진이 한줄 한줄 코드를 읽어가다가 var x = 1; 이라는 코드를 만났을 때, 환경 레코드에 등록된 변수인 것을 확인 한 후 그제서야 x를 1로 할당한다.

 

즉 실행 컨텍스트는 x라는 변수가 있구나 정도만 알고 있는 채로 일단 undefined를 할당해 놓는다. 그리고 런타임에서 자바스크립트 엔진이 x를 만난 순간, xundefined에서 1로 바뀐다. 그렇기 때문에 var로 변수를 선언하고 그 전에 변수를 참조했을 때 에러가 아닌 undefined를 출력한 것이다. 평가와 실행, 그리고 실행 컨텍스트라는 개념이 있기 때문에 이런 현상을 들어 마치 변수가 끌어올려지는 듯 하다 해서 호이스팅이라고 하는 것이다.

일시적 사각지대(Temporal Dead Zone: TDZ)

❓ let과 const는 호이스팅이 되지 않는가?

let과 const도 호이스팅이 된다고 했다. 아래 예시를 보자.

console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization

let x = 10;

var와 달리 undefined도 아닌 ReferenceError(참조 에러)를 출력했다. 참조에 실패했다는 얘기인데, letconst는 호이스팅이 발생하지 않는 것처럼 보인다. 하지만 이것도 평가와 실행, 그리고 실행 컨텍스트와 관련이 있다.

 

1. 이 코드를 다시 한번 코드를 뜯어 보면,

console.log(x);

let x = 10;

2. 먼저 코드가 평가되고 실행 컨텍스트 환경 레코드에 등록이 된다.

실행컨텍스트의 환경레코드 
x :

 

3. 하지만 let이나 const로 선언한 변수는 아무런 값으로도 초기화가 되지 않는다. var로 선언한 변수는 바로 undefined로 초기화가 되는 것과 차이가 있는데, 바로 이 차이 때문에 호이스팅이 되지 않는 것처럼 보이는 것이다. 실제로는 호이스팅은 되었지만 아무런 값도 참조할 수 없는 상태이다. 따라서 xReferenceError를 출력했던 것이고 이렇게 변수를 참조할 수 없는 구간을 일시적 사각지대라고 한다.

❗ 정리. var 사용을 지양해야 하는 이유

  1. var는 변수 중복 선언을 허용한다.
  2. 함수 레벨 스코프를 따르므로 개발 과정에서 는 에러가 없을지라도 기대되지 않은 결과를 발생시킬 수 있다.
  3. 호이스팅이 일어나도 에러를 출력하지 않아 개발 과정에서 예상치 못한 오류를 범할 수 있다.

🔗 참고

💡 회고

단순히 var를 왜 사용하면 안 되는지에 대해서 조금만 더 알아보려 했지만 자바스크립트의 많은 특징들을 확장해서 공부해야 했던 시간이었다. 변수에서 시작해서 자바스크립트가 스크립트 언어라는 점을 다시 살펴보고, 자바스크립트 엔진에 대해 알아보고, 다시 변수로 돌아왔다가 실행 컨텍스트까지 공부를 하면서 작은 개념이라도 결국 모든 개념들은 다 얽혀 있다는 것을 깨달았다. 역시 컴퓨터’과학’이다. 예전에 경영학과에서 산업공학과로 전과 했을 때, 과학에 대해 아무것도 모른 채로 주기율표를 공부하던 때가 떠오른다... 아무튼 이번 기회로 다음에 실행 컨텍스트에 대해 공부하게 된다면 좀 더 수월하게 공부할 수 있을 것 같다. 👊👊👊