변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위헤 붙인 이름이다. 즉 값의 위치를 가리키는 상징적인 이름이며, 값이 아닌 메모리 주소를 기억한다.
변수를 선언하는 키워드와 특징은 다음과 같다.
키워드 | 재선언 | 재할당 |
---|---|---|
var | O | O |
let | X | O |
const | X | X |
var
키워드의 호이스팅ES5까지 변수를 선언할 수 있는 방법은 var
밖에 없었다. 그러나 var
는 여러 문제가 있어 사용을 권장하지 않는다. 그 중 한 문제는 호이스팅에 의한 문제이다.
호이스팅이란, 변수의 선언을 런타임 이전에 실행하는 것이다. 변수의 선언과 값의 할당 시점이 다르기 때문에 문제가 발생한다. 변수 선언은 런타임이 이전에 먼저 실행되며, 값의 할당은 런타임에 할당된다. 따라서 런타임 때 변수의 선언보다 변수의 호출이 먼저일 경우 참조 에러가 발생하지 않고 초기화된 값인 undefined
로 나온다.
console.log(a); // undefined (런타임 전 이미 변수 선언/초기화된 상태)
a = 80; // 값의 할당
var a; // 변수 선언
console.log(a); // 80 (런타임 때 재할당)
변수 선언문 이전에 변수를 참조하는 것은 에러를 발생시키지는 않지만 코드의 흐름이나 가독성에 좋지 않기 때문에 권장하지 않는다. 이런 호이스팅 때문에 추후 문제가 발생할 수 있다.
let
, const
키워드의 호이스팅자바스크립트는 모든 선언을 호이스팅 한다. 그러나 ES6 때 도입된 let
, const
, class
를 사용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작한다.
console.log(b); // ReferenceError: b is not defined
let b;
let
으로 변수 선언보다 변수 참조를 먼저한 경우에는 참조 에러(ReferenceError)가 발생한다. 참조 에러는 변수가 초기화되기 전에 참조할 시에 발생하는 에러이다. 자바스크립트는 모든 선언을 호이스팅한다고 했는데, 선언 단계와 초기화 단계가 한번에 진행되는 var
키워드와 다르게 위 코드에서 undefined
로 출력되지 않는 이유는 뭘까?
그 이유는 선언 단계와 초기화 단계가 분리되어 실행되기 때문이다. 런타임 이전에 선언은 되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다. var
키워드와 순서를 비교해보자.
단계 | var 키워드의 호이스팅 순서 | 코드 |
---|---|---|
런타임 이전 | 선언 단계 + 초기화 단계 | var foo; foo = undefined; |
런타임 이후 | 할당 단계 | foo = 1; |
단계 | let 키워드의 호이스팅 순서 | 코드 |
---|---|---|
런타임 이전 | 선언 단계 | let foo; |
일시적 사각 지대(참조 불가능) | ||
런타임 이후 | 초기화 단계 | foo = undefined |
할당 단계 | foo = 1; |
따라서 let
키워드로 변수를 선언했을 때 초기화 단계까지 변수를 참조할 수 없다. 스코프의 시작 지점부터 초기화 단계 전까지 변수를 참조할 수 없는 구간을 일시적 사각지대(TDZ, Temporal Dead Zone)라고 한다.
// 선언 단계
console.log(foo); // ReferenceError: foo is not defined
// * * * 일시적 사각지대 * * * //
let foo; // 초기화 단계
console.log(foo); // undefined
foo = 1; // 할당 단계
console.log(foo); // 1
const
키워드는 상수를 선언하기 위해 사용된다. const
키워드로 선언한 변수는 코드 상에서 반드시 선언과 동시에 초기화가 이루어져야 한다. 그렇지 않으면 참조 에러가 발생한다.
const a; // SyntaxError: Missing initializer in const declaration
var
, let
으로 선언한 변수는 값을 재할당할 수 있다.
let score = 80;
score = 90;
변수 score
의 값을 80에서 90으로 재할당했다. 여기서 값 90으로 재할당하면서 새로운 메모리 공간을 확보하고 그 메모리 공간에 값 90을 저장한다. score
변수의 이전 값인 80은 이제 어떤 변수와도 연결되어 있지 않기 때문에 이런 불필요한 값들은 garbage collector에 의해 메모리에서 자동 해제된다.
이처럼 garbage collector는 어떤 식별자도 참조하지 않는 메모리 공간을 해제하는 기능이다. 자바스크립트는 garbage collector를 내장하고 있는 매니지드 언어로 이를 통해 메모리 누수를 방지한다.
참고) Mark and Sweep Algorithm
원시 타입은 변경 불가능한 값으로 원시 값을 변수에 할당한다면 확보된 메모리 공간에 값이 저장된다. 재할당을 하게 된다면 새로운 메모리 공간을 확보한 후 원시 값을 저장한다. 새로운 메모리 공간을 확보하면서 변수가 참조하던 메모리 공간의 주소도 바뀐다. 즉, 변경 불가능하다는 것은 확보된 메모리 공간에 저장된 값이 변경 불가능하다는 것으로 값에 초점이 맞춰진 설명이다.
참조 타입은 변경 가능한 값으로 값을 변수에 할당하면 확보된 메모리 공간에는 참조 값이 저장된다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소이다. 즉, 객체를 할당한 변수에는 생성된 객체가 저장된 메모리 공간의 주소가 저장된다. 변수는 이 값을 참조하여 실제 객체에 접근한다. 따라서 참조 타입은 할당된 변수의 메모리 공간의 주소를 통해 실제 객채에 접근하므로 변수의 참조 값은 변경되지 않기 때문에 재할당 없이 값을 변경할 수 있다. 상수 선언문인 const
로 선언하여도 값을 변경할 수 있는 이유이다.
자바스크립트 엔진은 원시 타입과 참조 타입을 메모리에 저장할 때 콜 스텍과 힙을 갖는다. 선언한 모든 변수는 우선 콜 스텍에 쌓이며 배열, 함수, 객체 같은 참조 타입은 힙에 영역이 할당된다. 여기서 콜 스텍에 쌓인 참조 타입의 변수는 힙의 메모리 주소를 참조하게 된다. 힙 메모리 영역은 크기를 동적으로 바꿀 수 있으므로, 콜 스텍 메모리가 아닌 힙의 메모리를 변경하는 것이기 때문에 참조 타입에서 값을 변경할 수 있게 되는 것이다.
참고) 콜 스텍과 힙