본문 바로가기

개발/Javascript

[Javascript] var, let, const의 차이점

 

Javascript에서 변수는 var, let, const 세 가지 키워드를 통해 선언할 수 있다.

 

지금부터 var, let, const 세 가지 키워드의 차이점을 알아보자.

 

 

 

var

 

var는 ES6 이전에 주로 사용되던 변수 선언 키워드로서 Function Scope로 동작한다는 특징을 가지고 있다.

 

먼저 Function Scope가 무엇인지 다음 두 코드를 비교해보자.

 

// Code 1

var name = "Yorr";

function setName() {
  var name = "John";
  console.log(`My name is ${name}`);
}

setName(); // My name is John
console.log(`My name is ${name}`); // My name is Yorr



// Code 2

var name = "Yorr";

if (name) {
  var name = "John"
  console.log(`My name is ${name}`) // My name is John
}

console.log(`My name is ${name}`) // My name is John

 

Code 1을 살펴보면 var로 선언된 name 변수는 "Yorr"이라는 값으로 초기화 된 후 setName이라는 함수 안에서 name의 값을 "John"으로 변경하고 있다.

 

그리고 해당 함수 실행 후에 콘솔로 name 변수를 출력하면 함수가 안에서는 "John"이 출력되지만 함수가 종료된 후에는 name에 여전히 "Yorr"이 담겨있는 것을 확인할 수 있다.

 

반면에 code 2에서는 if 문 안에서 name의 값을 "John"으로 변경 하면 name의 값이 완전히 "John"으로 변경된다.

 

그 이유는 Code 1에서는 var가 Function Scope 내에서 동작했지만 Code 2는 Function Scope가 아니기 때문에 외부 스코프에 존재하는 값을 변경했기 때문이다.

 

이러한 var의 특징은 전역 스코프를 오염시킨다는 문제를 가지게 되었다.

 

그리고 이러한 문제는 다음과 같이 즉시 실행 함수를 통해 var를 해당 스코프 내에 가두는 것으로 해결할 수 있었다.

 

(function () {
  var name = "Yorr";
  console.log(name); // Yorr
})();

console.log(name); // "Uncaught ReferenceError: name is not defined"

 

위의 코드를 확인해보면 즉시 실행 함수는 선언과 동시에 실행되고 닫히기 때문에 함수 내부에서 선언된 변수는 해당 스코프에 갇히게 되었고, 외부에서 해당 변수를 사용하려는 시도가 일어날 경우 참조 에러가 발생하는 것을 확인할 수 있다.

 

즉, 즉시 실행 함수를 사용하면 내부의 변수 선언이 외부 스코프를 오염시키는 것을 방지할 수 있는 것이다.

 

하지만 즉시 실행 함수만으로 모든 것이 해결되는 것은 아니다.

 

다음 코드를 살펴보자.

 

(function () {
  name = "Yorr";
  console.log(name); // Yorr
})();

console.log(name); // Yorr

 

위의 코드는 즉시 실행 함수를 통해 스코프를 제한했는데도 불구하고 에러가 발생하지 않고, 심지어 함수 외부에서 name의 값이 출력되고 있다.

 

그 이유는 var 키워드를 사용하지 않고 변수를 선언할 경우 자바스크립트 엔진이 외부 스코프에서 해당 변수를 찾으려고 하기 때문이다.

 

그리고 전역 스코프까지 해당 변수를 찾지 못한다면 자바스크립트 엔진은 해당 변수를 전역 스코프에 선언해버린다.

 

var가 가지고 있는 이러한 동작을 제대로 이해하기 위해서는 먼저 호이스팅(Hoisting) 이라는 개념을 알아야 한다.

 

호이스팅이란 말 그대로 "끌어올리다" 라는 의미를 가지고 있다.

 

먼저 다음 코드를 통해 확인해보자.

 

console.log(name); // undefined
var name = "Yorr";
console.log(name); // Yorr

 

위의 코드를 실행시켜 보면 에러가 발생하지 않고 정상적으로 동작하는 것을 확인할 수 있다.

 

첫 번째 줄에서 변수가 선언되기 전에 사용되었는데 불구하고 에러가 발생하지 않고 undefined라는 값을 반환하는 이유는 무엇일까?

 

그 이유는 자바스크립트 엔진이 var 선언을 최상위로 끌어올리기 때문이다.

 

즉, 위의 코드는 자바스크립트 엔진에 의해 다음과 같이 해석된다.

 

var name;
console.log(name); // undefined
name = "Yorr";
console.log(name); // Yorr

 

위의 코드를 보면 알 수 있듯 자바스크립트 엔진은 호이스팅에 의해 var의 선언을 최상위로 끌어올린다. 하지만 선언을 끌어올릴 뿐 할당은 끌어올리지 않기 때문에 두 번째 줄에서 undefined가 반환되는 것이다.

 

그리고 이러한 var의 호이스팅을 막기 위해서는 스코프 내에서 use strict를 사용해야 한다.

 

'use strict';

console.log(name); // Uncaught ReferenceError: name is not defined
var name = "Yorr";
console.log(name);

 

use strict 키워드를 사용하니 이전과 같이 선언하지 않은 변수를 사용했을 때 참조 에러가 발생하는 것을 확인할 수 있다.

 

use strict는 Javascript 문법을 엄격하게 적용하겠다는 의미의 키워드로 위와 같이 선언되지 않은 변수의 사용 또는 선언자 없는 변수에 대한 할당 등에 대해 에러를 발생시킨다. (use strict는 항상 코드의 최상단에 작성해야 하며 실행 시점 이후에는 취소할 수 없다.)

 

 

 

지금까지 var가 가진 특징과 문제점을 살펴봤다. 사실 특징보다는 단점만을 이야기 한 것이 사실이다. 개인적으로 var가 가진 장점은 "없다"라고 말하고 싶다. 

 

그 이유는 var를 그냥 사용하게 되면 Function Scope와 호이스팅으로 인해 의도하지 않은 문제가 발생할 가능성이 크고, 이를 제어하기 위해서는 즉시 실행 함수나 use strict 같은 추가적인 프로그래밍이 필요하기 때문이다.

 

또한 ES6에 let이 등장하면서 use strict 키워드나 즉시 실행 함수를 사용하면서까지 var를 사용할 이유가 없어졌기 때문에 더 이상 var는 사용되지 않게 되었다. (정확히는 사용할 이유가 없어졌다.)

 

 

 

let과 const의 등장

 

지금부터는 ES6 이후 등장한 let과 const에 대해 알아보자.

 

먼저 let은 var와 같은 변수 선언 키워드이다. 하지만 var와 달리 let과 const는 Block Scope를 가진다.

 

let은 먼저 var와 동일하게 값의 재 할당이 가능하다.

 

let name = "Yorr";
name = "John";

console.log(name); // John


하지만 var와 달리 재 선언은 불가능하다.

let name = "Yorr";
let name = "John"; // Identifier 'name' has already been declared

console.log(name);

 

또한 let은 선언되지 않은 변수를 사용할 수 없다.

 

console.log(name); // Uncaught ReferenceError: name is not defined
let name = "Yorr";
console.log(name);

 

이러한 특징을 보고 let은 var와 달리 호이스팅이 일어나지 않기 때문에 use strict 키워드를 사용하지 않아도 선언하지 않은 변수를 사용했을 때 에러가 발생한다고 생각할 수 있다.

 

심지어 많은 자료에서 여전히 let과 const는 호이스팅이 일어나지 않는다고 설명하고 있지만 이것은 잘못된 이야기이다.

 

let과 const 또한 호이스팅이 발생하지만 코드 실행이 변수가 실제 있는 위치에 도달할 때 까지 사용을 허용하지 않기 때문에 참조 에러가 발생한다

 

실제 이러한 let과 const의 동작을 이해하기 위해서는 Lexical EnviromentTDZ(Temporal Dead Zone) 개념을 알아야 하지만 해당 글에서 다루기에는 주제를 벗어나는 내용이기 때문에 지금은 let과 const도 호이스팅이 이루어지지만 초기화가 이루어 지기 전까지 사용이 제한된다는 정도로 이해하자. (Lexical Enviroment와 TDZ에 대해서는 추가로 포스팅을 작성할 예정이다.)

 

그리고 앞서 말한대로 let과 const는 Block Scope를 가지기 때문에 다음과 같이 Block 내부에 선언된 변수는 외부 스코프에 영향을 끼치지 않는다.

 

let name = "Yorr";

if (name) {
  let name = "John";
  console.log(name); // John
}

console.log(name); // Yorr

 

 

 

마지막으로 const에 대해 알아보자.

 

const는 사실 더 이상 말할 것이 없을 정도로 let과 유사하다.

 

다만 상수 선언 키워드로 사용되기 때문에 값을 재 할당하는 것이 불가능하고, 선언과 할당이 동시에 이루어져야 한다.

 

// const 키워드는 재 할당을 할 수 없다.
const name = "Yorr";
name = "John"; // Uncaught TypeError: Assignment to constant variable.

 

// const 키워드는 선언과 할당이 동시에 이루어져야 하기 때문에 선언만 할 수 없다.
const name; // Uncaught SyntaxError: Missing initializer in const declaration

 

하지만 객체(Object)에 대해서는 식별자나 프로퍼티명을 통해 직접 접근해서 값을 변경할 수 있다.

 

const obj = {
  name: "Yorr"
};

obj.name = "John";
console.log(obj); // John

 

위의 설명을 통해 객체에 대해 식별자나 프로퍼티명을 통해 값을 변경할 수 있다면 굳이 let과 같은 변수 선언이 아닌 상수 타입을 사용해야 하냐는 생각을 할 수 있다.

 

다음 코드를 확인해보자.

 

const obj = {};
obj = function () {}; // Uncaught TypeError: Assignment to constant variable.

 

위 코드에서 obj라는 객체가 이미 const로 선언이 되었기 때문에 식별자에 새 리터럴 값을 할당하는 것이 불가능하다.

 

그렇기 때문에 다음과 같은 코드도 불가능하다.

 

const obj = {
  name: "Yorr"
};

obj = "John"; // Uncaught TypeError: Assignment to constant variable.

 

즉, 값을 const로 선언해서 사용하는 이유는 값이 가진 타입의 변경을 제한하겠다는 것이다.

 

실제로 Node.js나 Vue.js와 같은 Javascript 기반의 언어나 프레임워크를 사용하다 보면 대부분의 값을 const로 선언해서 사용하는 것을 볼 수 있다.

 

그 이유는 사실 값을 변경하는 변수를 사용할 일이 생각보다 많지 않고, const를 통해 선언한 값의 타입을 유지하는 것이 개발에 있어 많은 실수를 방지해주기 때문이다. (특히 Javscript 이기 때문에 더 그렇다. 그래서 TypeScript가 굉장히 많은 사랑을 받고 있는 것 같다.)

 


 

 

 

안녕하세요. 평범한 대학생 개발자 yorr입니다.

포스팅을 읽고 궁금한 점 또는 문의가 있으신 분은 메일 또는 댓글을 남겨주세요.

 

Mail: twysg@likelion.org

Github: https://github.com/sangyeol-kim