Language/JavaScript

JavaScript 기초) var, let, const

snowe 2021. 2. 25. 23:15

ES6부터 let과 const가 추가됨에 따라,  var가 유일한 변수 선언 방법이었던 JavaScript에는 총 세가지의 변수 선언 방식이 존재하게 되었습니다.

오늘은 이 세가지 변수 선언 방식에 대해 알아보겠습니다.

 

+Hosting에도 차이가 있다(SOPT 27th 서버 세미나 자료 참조).

1. Scope

Function-level scope

var는 Function Scope에 해당됩니다.

대부분의 프로그래밍 언어는 Function Scope가 아닌 Block Scope에 해당합니다. 하지만 JavsScript의 경우 ES6전까지 Function Scope가 전부인 언어였으며 다음과 같은 코드 형식을 가졌습니다.

//MARK: -Function Scope
function scopeTest() {
	for(var i=0; i<5; i++){
    	var answer = i;
    }
    
    console.log(answer); // 4
}

answer는 for문 안에서 선언되었으니 블럭 외부에서 작성된 console문에서는 레퍼런스 오류가 날 것이라고 생각할 수 있지만, 로그에는 에러가 아닌 '4'가 출력됩니다.

 

이것이 바로 Function Scope의 특징입니다. Function Scope는 하나의 블록({})이 아니라, 함수 내에서 선언된 모든 변수들을 참조할 수 있는 것을 뜻합니다. 그렇기 때문에 우리는 for문을 통해  5가 된 i가 answer에 들어있고 그것을 출력할 수 있었습니다.

 

Function-level scope를 요약하면 다음과 같습니다.

함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

var foo = 123; // 전역 변수

console.log(foo); // 123

{
  var foo = 456; // 전역 변수
}

console.log(foo); // 456
이러한 Function Scope의 특성은 전역 변수를 남발할 수 있는 가능성을 높이게 됩니다.

 

Block-level scope

//MARK: -Block Scope
function scopeTest() {
	for(var i=0; i<5; i++){
    	let answer = i;
    }
    
    console.log(answer); //ReferenceError: answer is not defined
}

아까와 같은 코드이지만 answer의 선언을 let으로 바꾸었습니다. let과 const는 Function scope가 아닌, Block scope이기 때문에 for문 블럭 안에서 선언된 answer의 경우 블럭 밖의 코드인 console.log()가 참조할 수 없는 변수가 됩니다. 따라서 참조 오류가 나게 되는 것이죠.

 

Block-level Scope를 요약하면 다음과 같습니다.

모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.

let foo = 123; // 전역 변수

{
  let foo = 456; // 지역 변수
  let bar = 456; // 지역 변수
}

console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined

 

 

2. 변수 재선언 및 재할당

var

//MARK: -변수 재선언
var name = "wonseok";
console.log(name); // 출력값: wonseok

var name = "Tom";
console.log(name); // 출력값: Tom

//MARK: -변수 재할당
name = "Jerry"
console.log(name); // 출력값: Jerry

var의 경우 변수 재선언 및 재할당이 가능합니다. 이러한 특성은 간단한 테스트 단계에서 그 편리함 때문에 장점으로 작용 될 수 있지만, 의도하지 않은 변수값의 변경이 일어날 가능성이 크기 때문에 꼭 필요한 경우가 아니라면, var의 사용을 자제 하는것이 좋습니다.

 

 

var에서 발생할 수 있는 여러 문제점들을 보완하고자 나온것이 let과 const입니다.

 

let

let의 경우 변수 재선언은 불가하지만 변수 재할당은 가능합니다.

//MARK: -변수 재선언
let name = "wonseok";
console.log(name); //출력값: wonseok

let name = "Tom";
console.log(name); //Uncaught SyntaxError: Identifier 'name' has already been declared

//MARK: -변수 재할당
name = "Jerry"
console.log(name); //출력값: Jerry

const

const의 경우 변수 재선언과 변수 재할당 모두 불가합니다.

또한 const 키워드로 선언한 변수는 선언과 동시에 초기값을 지정해주어야 합니다.

//MARK: -변수 재선언
const name = "wonseok";
console.log(name); //출력값: wonseok

const name = "Tom";
console.log(name); //Uncaught SyntaxError: Identifier 'name' has already been declared

//MARK: -변수 재할당
name = "Jerry";
console.log(name); //Uncaught TypeError: Assignment to constant variable.


//MARK: -초기값 설정
const ram;
console.log(ram); //SyntaxError: Missing initializer in const declaration

 

 

3. Hoisting

호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성입니다.

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

console.log(bar); // ReferenceError: bar is not defined
let bar;

console.log(ram);
const ram = 19; // ReferenceError: ram is not defined

var

1,2 라인을 보게 되면 console.log(foo);에서 foo라는 변수를 선언하기 전에 호출하였지만 Hoisting으로 인해 에러가 발생하지 않았습니다. 

 

let, const 

하지만 var 키워드로 선언된 변수와는 달리 let,const 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생합니다. 이는 let,const 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문입니다.

 

 

왜 그런걸까요?

 

 

우리가 선언한 변수가 생성되기까지는 총 3단계를 거쳐야합니다.

 

  • 선언 단계: 변수를 실행 컨텍스트의 변수 객체에 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.
  • 초기화 단계: 변수 객체에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
  • 할당 단계: undefined로 초기화된 변수에 실제 값을 할당한다.

 

var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어집니다. 즉, 스코프에 변수를 등록(선언 단계)하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화(초기화 단계)합니다. 따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않습니다. 

 

let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행됩니다. 즉, 스코프에 변수를 등록(선언단계)하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어집니다. 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생합니다. 초기화 단계를 거치기 전이므로 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문입니다. 따라서 스코프의 시작 지점부터 초기화 시작 지점 전 까지는 변수를 참조할 수 없다. 이 구간이 바로 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’입니다.

 

 

마치며: var, let, const 중 무엇을 고르는 것이 좋을까?

코드에는 전역 변수가 많을 수록, 스코프가 광범위 할수록 유지보수가 어려워집니다. 그 말은 Function scope 보다는 Block scope를 사용하는 것이 좋다는 말이겠죠?

 

따라서 변수 선언 시에는!

  • ES6를 사용한다면 var 키워드는 사용하지 않는다.
  • 재할당이 필요한 경우에 한정해 let 키워드를 사용한다. 이때 변수의 스코프는 최대한 좁게 만든다.
  • 변경이 발생하지 않는(재할당이 필요 없는 상수) 원시 값과 객체에는 const 키워드를 사용한다. const 키워드는 재할당을 금지하므로 var, let 보다 안전하다.

 

 

 

참고

 

 

let, const | PoiemaWeb

ES5까지 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는 것이었다. var 키워드로 선언된 변수는 아래와 같은 특징이 있다. 이는 다른 언어와는 다른 특징으로 주의를 기울이지 않으면

poiemaweb.com