
Intro
리액트의 동작 원리를 공부하다 보면, 때때로 자바스크립트 자체에 대한 이해가 부족하다는 느낌을 받을 때가 있다. 리액트의 내부 메커니즘을 제대로 이해하기 위해서는 자바스크립트의 기본 동작 원리를 숙지하는 것이 중요하다.
리액트 학습 내용을 정리하기 전에, 자바스크립트의 개념을 빠르게 되짚어보는 것을 목표로 2024년을 마무리하려 한다.
1. 변수(Variable)
변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름을 말한다. 변수명은 해당 메모리 공간을 가리키는 식별자(identifier)가 되며, 이를 통해 우리는 프로그램 실행 중에 값을 저장하고 참조할 수 있다.
2. 변수의 선언(Declaration)
변수의 선언이란 간단하게 말하면 변수를 생성하는 것이다. 자세히 말하자면, 변수의 선언은 값을 저장하기 위한 메모리 공간을 확보하고 변수 이름과 확보된 메모리 공간의 주소를 연결해서 값을 저장할 수 있게 준비하는 것이다.
자바스크립트에서 변수를 선언할 때는 var, let, const 키워드를 사용한다.
// 변수 선언 var score;
score라는 변수를 선언해보자. 자바스크립트는 변수 선언을 2단계에 거쳐 수행한다.
- 선언: 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알린다
- 초기화: 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로
undefined를 할당해 초기화한다
var 키워드를 사용하면 변수 선언과 초기화가 동시에 이뤄진다. 따라서 var 키워드로 선언한 변수는 할당하기 전에 참조해도 undefined가 반환된다. 선언되지 않은 변수를 참조하려 할 경우에는 ReferenceError가 발생한다.
3. 변수의 호이스팅(Hoisting)
console.log(score); // undefined var score; // 변수 선언
지금까지 정리했던 내용이 무색하게도, 위 코드를 실제로 콘솔에서 입력해보면 score 변수를 선언하기도 전에 출력했음에도 불구하고 ReferenceError가 발생하지 않는다. 이를 호이스팅(Hoisting) 이라 하며, 호이스팅의 원인은 변수 선언의 실행 시점에 있다.
자바스크립트 엔진은 소스 코드를 실행하기 전, 변수 선언을 포함한 모든 선언문(변수 선언문, 함수 선언문 등)을 먼저 실행한다. 즉 자바스크립트 엔진은 변수 선언의 위치가 소스 코드의 어디에 있든 상관 없이 다른 코드보다 먼저 실행한다. 따라서 변수 선언문이 출력 코드보다 아래에 있더라도, 변수가 선언되어 undefined가 출력될 수 있는 것이다.
4. 변수의 값 할당(Assignment)
변수에 값을 할당할 때는 할당 연산자 =를 사용한다. 할당 연산자는 우변의 값을 좌변의 변수에 할당한다.
var score; // 변수 선언 score = 100; // 값 할당 var score2 = 100; // 선언과 값의 할당
변수의 선언과 값의 할당을 2개의 문으로 나누던, 하나의 문으로 단축하던 코드는 정확히 동일하게 동작한다. 하지만 주의할 점은 변수 선언과 값의 할당 시점이 다르다는 것이다. 변수 선언은 소스코드가 실행되는 런타임 이전에 먼저 실행(호이스팅)되지만, 값의 할당은 소스코드가 순차적으로 실행되는 시점인 런타임에 실행된다.
console.log(score); // undefined score = 80; var score; console.log(score); // 80
위 코드를 콘솔에 입력해보면, 첫 출력에서는 score가 호이스팅되어 초기화 값인 undefined가 출력된다. 그리고 다음 출력에서는 초기화되었던 변수에 값이 할당되어 80이라는 값이 출력된다.
5. 함수 스코프(Function Scope)
자바스크립트에서 스코프는 변수의 유효 범위를 의미하며, 변수의 접근 가능 범위를 결정한다. 함수 스코프란 변수가 선언된 함수 내부에서만 유효한 스코프를 의미한다. var 키워드로 선언한 변수는 함수 스코프를 가진다.
function foo() { var a = 10; console.log(a); // 10 } console.log(typeof a); // undefined, 함수 외부에서는 접근 불가
위 예제에서 a 변수는 foo 함수 내부에서만 접근 가능하며, 함수 호출이 종료되면 더 이상 외부에서 참조할 수 없다. 그러나, 함수 스코프를 가지는 변수(var)를 함수가 아닌 if나 for 같은 블록 안에서 선언해도 해당 블록을 벗어나면 변수가 유지되는 문제가 발생할 수 있다.
6. 블록 스코프(Block Scope)
블록 스코프란 {}로 묶인 코드 블록 내에서만 변수가 유효한 스코프를 의미한다. let과 const는 블록 스코프를 지원한다.
if (true) { let b = 20; console.log(b); // 20 } console.log(typeof b); // undefined, 블록 밖에서는 접근 불가
위 예제에서 b 변수는 if 블록 내부에서만 유효하며, 블록을 벗어나면 사라진다. 이를 통해 변수의 생명주기를 더 명확하게 관리할 수 있으며, 불필요한 변수 누출을 막는다.
7. var 키워드로 선언한 변수의 문제점
var는 ES5까지 변수를 선언할 수 있는 유일한 방법이었다. 하지만 다음과 같은 특징으로 인해 잘 사용하지 않는 추세이다.
- 함수 스코프(Function Scope)로 인한 혼란:
var는 함수 스코프를 갖지만 블록 스코프는 지원하지 않는다. 이는 블록({}) 내부에서 선언한 변수가 의도치 않게 외부에서도 접근 가능해져 코드 가독성과 유지보수를 어렵게 만든다.
if (true) { var x = 10; } console.log(x); // 10, 블록 밖에서도 x에 접근 가능
-
변수 호이스팅으로 인한 혼란:
var는 호이스팅으로 인해 변수 선언 전 참조가 가능하며, 이는 종종 예측하기 어려운 코드 동작을 야기한다. -
중복 선언 허용:
var키워드는 같은 스코프 내에서 변수를 중복 선언할 수 있다. 이는 의도치 않은 변수 덮어쓰기를 야기할 수 있다.
var score = 100; var score = 200; // 오류 없이 변경됨 console.log(score); // 200
8. let과 const
let과 const 키워드는 ES6(ECMAScript 2015)부터 도입되어 var의 단점을 보완한다.
let
-
블록 스코프(Block Scope):
let키워드로 선언한 변수는 블록 레벨 스코프를 가지므로,{}로 감싸진 코드 블록 내에서만 유효하다. -
중복 선언 불가: 같은 스코프 내에서
let키워드로 동일한 변수를 재선언하면 에러가 발생한다. -
TDZ(Temporal Dead Zone): TDZ는 변수가 선언되기 전 일시적으로 접근할 수 없는 영역을 의미한다.
let변수는 선언 단계와 초기화 단계가 분리되며, 초기화 이전에 참조하면ReferenceError를 발생시켜 예측 가능한 코드 동작을 유도한다.
// TDZ 예시 console.log(x); // ReferenceError let x = 5; // 반면 var는 undefined 출력 console.log(y); // undefined var y = 5;
const
const는 상수(Constant) 값을 할당하기 위해 사용하며, 기본적으로 let의 특성을 모두 가지고 다음과 같은 특징을 추가로 갖는다.
- 재할당 불가:
const로 선언한 변수는 한번 값을 할당하면 이후 변경할 수 없다.
const PI = 3.14; PI = 3.14159; // TypeError
- 참조형 데이터에 대한 불변성 보장 X:
const로 선언한 객체나 배열은 참조 자체를 변경할 수 없지만 내부 프로퍼티 변경은 가능하다.
const obj = { a: 1 }; obj.a = 2; // 가능
var vs let vs const
기본적으로 변수 선언 시 const를 우선적으로 사용하며, 재할당이 필요한 경우에만 let을 사용한다. 그리고 var 사용은 최대한 피한다.