백엔드 개발자(node.js)가 되는 과정

var, let, const의 차이와 호이스팅, 스코프

soopy 2024. 2. 8. 17:22
728x90

자바스크립트에서 var, let, const 모두 호이스팅을 한다는 사실을 뒤늦게 알게 되었다. 사실 호이스팅 자체가 잘 이해가 가지 않았기 때문인 것 같다. "호이스팅은 선언된 변수나 함수를 코드의 상단으로 끌어올린다." 라는 표현을 쓰는데 이전에는 "var apple;을 선언한 뒤 console.log(apple)을 입력하고, 그 아랫줄에 apple = 1이라고 초기화하면 console.log(apple)은 1이 출력되어야 한다는 말인가?" 라고 받아들여졌다. 하지만 틀린 말이다. 실제로 출력값은 undefined가 나오기 때문에 별 문제가 없어보인다. 그럼 상단으로 끌어올려지는건 대체 뭐란 말인가?

호이스팅

일단 호이스팅은 스크립트의 컴파일 단계에서 벌어지는 일이라고 한다. 컴파일 과정에서 var, let, const 변수가 호이스팅 된다는 말은 "선언된" 변수가 가장 윗줄로 끌어올려 진다는 말이었다. 여기에서 선언과 초기화에 대한 이해가 필요하다. 먼저 let apple; 또는 var apple; 이라고 코드를 작성하면 이를 apple 변수가 "선언"되고, undefined라는 값으로 "초기화" 되었다고 한다. 선언과 초기화 단계가 분리되어 있다는 점을 기억하자. 물론 let apple = 1로 입력하면 이 때는 선언되고, 1로 초기화된 것을 의미한다.

이제 호이스팅이 되면 var와 let에 어떤 차이가 있는지 살펴보자.

console.log(apple); // 레퍼런스 에러: Cannot access 'apple' before initialization
let apple;


먼저 let apple 의 경우 해당 코드가 작성된 line에 당도했을 때 undefined가 초기화 된다. 그러므로 호이스팅된 apple은 "선언"된 시점의 apple만 끌어올려 진다고 볼 수 있다. 그래서 apple 변수는 "일시적인 사각지대(temporal dead zone)" 라는 영역에 존재하게 되어 console.log(apple) 출력 시 'Cannot access 'apple' before initialization' 에러가 발생한다. 이게 현재의 우리에게 익숙한 결과일 것이다.

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

하지만 var apple의 경우는 조금 다르다. apple이 호이스팅 되면 선언한 시점에 초기화도 함께 이루어진다. let의 경우 실제로 let apple;이라는 코드를 작성한 line에 당도했을 때 초기화가 이루어진다는 점에서 이 둘은 차이가 있다. 그래서 console.log(apple)이 undefined를 출력하게 되는 것이다. 

console.log(apple); // undefined
var apple = 10;

혹시나 해서 위 코드에서 console.log(apple)이 10이 출력되리라 생각할 수도 있을 것이다. 하지만 그렇지 않다. 호이스팅되는 것은 apple에 대한 선언인데, var의 경우 선언과 동시에 초기화가 이루어진다는 점을 되새겨보자. 그러므로 var apple = 10은 사실상 10으로의 초기화가 아니라 10으로의 값 변경이라고 볼 수 있다. 초기화 시점이 호이스팅된 시점이기 때문이다.

console.log(apple);
const apple; // SyntaxError: Missing initializer in const declaration

참고로 const는 var와 let보다 좀 더 엄격한 면이 있다. 우선 위 코드와 같이 const apple에 대한 값을 설정하지 않으면 문법 오류를 내버린다. 그래서 초기값을 지정해 주어야 선언이 발생한다고 볼 수 있다. 그리고는 값이 초기화되는 시점도 let과 동일하지만 const는 초기화 된 이후 값을 변경할 수 없다는 차이가 있다.

정리하자면 let, const vs var는 선언된 변수의 초기화 시점에 차이가 있다. 라고 받아들이면 되겠다.

위 호이스팅 문제는 var를 안쓰면 크게 헷깔릴 문제가 아니며 나처럼 파이썬으로 코딩 공부를 시작했다면 let, const만 써야지라고 생각하면 호이스팅 문제로 혼란을 겪을 일이 크게 없을 것이다.

스코프

var vs let, const 문제는 스코프의 차이에 의해서도 발생한다.
먼저 스코프는 변수의 유효 범위를 말한다. 초기화된 변수를 사용할 수 있는 범위가 상황에 따라 다르다는 의미이다. 그래서 스코프는 글로벌, 함수 스코프, 블록 스코프 라는 범위로 구분한다. 글로벌은 스크립트 전체 영역을 의미하고, 함수는 말그대로 함수(function)영역, 블록은 if, while, for문 영역을 말한다. 이렇게 각 영역이 구분되어 있음을 기억하고서 계속 이어나가자.

먼저 함수와 블록은 글로벌 영역의 부분집합에 속한다. 그래서 글로벌 영역에서 초기화된 변수는 함수에서도, if문, for문, while문, 어디서든지 사용이 가능하다.

const apple = '애플'

function fx1() {
    console.log(`함수 안에 ${apple}`)
}

fx1()

if (true) {
    console.log(`if문 안에 ${apple}`)
}
함수 안에 애플
if문 안에 애플

하지만 그 반대는 제한이 있다. 함수스코프와 블록스코프에서 선언된 변수의 영향력은 그 바깥 영역인 글로벌로 뻗어나가지는 못한다. 태생이 부분집합에 속하는 스코프이기 때문에 그곳을 벗어날 수 없다는 개념이 자바스크립트에서는 일반적으로 적용되고 있다.

if (true) {
    let apple = '애플'
}
console.log(apple) // apple is not defined

// 함수도 마찬가지다.

그런데 var는 희안하게도 이 개념을 지키지 않았다. 바로 블록스코프 내에서 선언된 var 변수가 글로벌 변수까지 영향력이 닿는 것이었다.(함수 스코프에 대해서는 선언된 var 변수가 함수 스코프를 벗어나지 못한다.)

console.log(x) // undefined

if (true) {  
    var x = 3;  
}  

console.log(x) // 3

위 코드를 보면 if문(블록스코프) 안에 선언된 var x 변수가 글로벌에서 undefined를 출력하는 것을 확인할 수 있다. 이 문제가 바로 var의 호이스팅 특징 때문에 발생한 것이다. 앞서 말한 바와 같이 var는 호이스팅 되면서 선언과 동시에 초기화된다. 그러니 console.log(x)가 undefined를 출력하는 것이다. 그리고 블록스코프 안에서 선언된 변수의 값을 3으로 변경하면서 호이스팅된 x값이 영향을 받고, 이후 console.log(x)에는 3이 출력된다. 그래서 var의 스코프 개념이 다소 혼잡하여 오류를 범할 가능성이 그만큼 높은 것이다. 

console.log(x) // x is not defined

if (true) {  
    let x = 3;  
}  

console.log(x) // x is not defined

let이나 const를 쓰면 사고하기 편리하다. "블록스코프 안에서 선언되었으니 밖을 벗어나지 못할거야" 라는 개념만 이해하면 위 코드가 매우 쉽게 다가오기 때문이다. 이러한 이유로 var 사용을 지양하는 것이다.

let x = 10

console.log(x) // 10

if (true) {  
    x = 3;  
}  

console.log(x) // 3

 

그렇다면 if문을 통해서 글로벌 값을 변경하고 싶을 수도 있을 텐데 그런 경우 위와 같이 글로벌에서 변수를 선언한 뒤 해당 변수를 함수나 블록스코프에서 변경하면 된다. 글로벌 변수는 어디든 갈 수 있기 때문이다.

 

(부록) 파이썬과 자바스크립트의 스코프 차이

# python
if True:  
    x = 3  
print(x) # 3 출력
// javascript
if (true) {  
  let x = 3;  
}  
console.log(x) // x is not defined

 

파이썬을 공부한 뒤 자바스크립트를 공부한 나로서 재밌는 점을 발견했다. 위와 같이 파이썬 변수는 if문에서 선언되어도 글로벌로 뻗어나온다. 사실 파이썬은 함수와 모듈 단위로 변수의 스코프가 구분되다보니 if문은 그냥 글로벌로 취급된다. 

728x90
728x90