과거에 쓰이던 고전적인 키워드인 var와
현대 표준인 let, const의 기본적인 것들 말고도
심층적인 개념들을 개인적으로도 더 자세히 공부해 볼 겸
비교하는 글을 써봤습니다.
실행 컨텍스트
자바스크립트는 단순한 스크립트 언어를 넘어
매우 독창적인 메모리 관리 매커니즘을 가지고 작동합니다.
개발자가 작성한 소스 코드가 JavaScript V8 엔진에 의해 실행될 때
가장 먼저 생성되는 개념이 바로 실행 컨텍스트(Execution Context)입니다.
실행 컨텍스트는 코드가 실행되기 위해 필요한
모든 배경 정보와 환경 데이터를 집약해 두는 가상의 논리적 영역입니다.
자바스크립트 엔진은 코드를 한 줄씩 읽어 내려가는 실행 단계에 돌입하기 전,
준비 단계에 해당하는 컴파일 단계를 선행하여 실행합니다.
이 준비 과정에서 전체 소스 코드를 훑어보며
변수 선언문과 함수 선언문을 낱낱이 찾아내어
메모리 공간에 먼저 등록하는 작업을 수행합니다.
이러한 내부적인 준비 흐름 덕분에 우리는 코드가 실행되기도 전에
특정 변수나 함수가 메모리 상에 이미 존재하게 되는 신비로운 현상을 경험하게 됩니다.
그리고 그것에 의해 생기는 현상들을 호이스팅 이라고 합니다.
호이스팅
우리가 자바스크립트를 사용 할 때 흔히 겪는 호이스팅(Hoisting) 현상은
선언문이 코드의 최상단으로 끌어올려진 것처럼 착각을 일으키는 엔진의 메커니즘입니다.
실제로 코드가 위로 이동하는 것은 아니며, 앞서 언급한 컴파일 단계에서
선언문들을 메모리 주소에 먼저 매핑해 두었기 때문에 발생하는 현상입니다.
그러나 변수를 선언할 때 어떤 키워드를 사용하느냐에 따라
호이스팅이 일어났을 때의 내부 동작 방식은 완전히 180도 다르게 전개됩니다.
호이스팅이 발생했을 때 각 변수가 다르게 행동하는 원인은
자바스크립트 엔진이 변수를 만드는 단계적 규칙이 다르기 때문입니다.
첫째로 var 키워드는 소스 코드가 실행되기 전
컴파일 단계에서 선언과 동시에 undefined로 초기화까지 한 번에 완료됩니다.
이 때문에 실제 값이 할당되기 전 상단 코드에서 변수를 참조하더라도
에러가 발생하지 않고 임시 값을 정상적으로 출력해 주는 특징이 있습니다.
둘째로 let 키워드는 컴파일 단계에서
변수의 이름만 등록하는 선언 단계만 수행할 뿐 초기화는 진행하지 않습니다.
실제 소스 코드 상에서 let이 적힌 물리적인 행에 도달하여
브라우저가 해당 엔진 코드를 실행하는 순간 비로소 초기화가 이루어집니다.
셋째로 const 키워드는 선언과 동시에 실제 값을 무조건 할당해야만 하는
가장 엄격한 상수의 성격을 지니고 있습니다.
let과 마찬가지로 실제 선언문 위치에 도달하기 전까지는 절대 초기화되지 않으며,
컴파일 시점에 무조건 값 할당문이 결합되어 있어야만 문법 에러를 방지할 수 있습니다.
이제 각 변수 키워드별 특징들을 알아보겠습니다.
var
var 는 초창기부터 쓰인 오래된 방식입니다.
선언 후에도 재할당이 가능한 변수 방식입니다.
그리고 중복해서 선언이 가능하고, 마지막에 할당된 값이 변수에 저장됩니다.
JavaScript
console.log(a); var a = 10; var a = 20; console.log(a); undefined20
컴파일 단계에서 미리 선언하고
undefined로 초기화해놓기 때문에,코드 파일에서 선언 전에 참조를 하게 작성하여도 참조 오류가 뜨는 대신
undefined를 출력하게 됩니다.
변수 스코프 유효범위는 함수 레벨 스코프(function-level scope) 입니다.
함수 내부에 선언된 변수만 지역변수로 한정하며, 나머지는 모두 전역변수로 간주합니다.
JavaScript
var a = 'I love'; console.log(a) I love if(a == 'I love'){ var b = 'banana'; //if, for 등은 블럭 레벨, 블럭 외부에서도 참조 가능 } console.log(b) banana function somuch(value){ var c = value; //함수 안에서만 참조 가능 } somuch('somuch') console.log(c) ReferenceError: c is not defined
변수 a(스크립트 최상단 전역에서 선언) 랑
변수 b(if 문 내부 '블록' 에서 선언) 는 스크립트 최상단에서 출력이 잘되지만,
변수 c('함수 내부' 에서 선언) 는 참조가 불가능해서 에러가 난 모습입니다.
let, const
let 과 const 는 요즘 제일 많이 쓰이는 방식입니다.
let 은 var 처럼 선언 후에도 재할당이 가능한 변수이지만,
const 는 선언 후 재할당이 불가능한 상수 이며,
처음 선언하는 동시에 값도 정의해 주어야 합니다.
선언 방식 테스트
JavaScript
let a undefined const b SyntaxError: Missing initializer in const declaration
재할당과 재선언 가능 여부 테스트
JavaScript
let a = 10; a = 20; console.log(a) 20 const b = 10; b = 20; TypeError: Assignment to constant variable. let a = 'you can't do that'; //위에서 let으로 선언한 a 재선언 SyntaxError: Identifier 'a' has already been declared const b = 'please stop it'; //위에서 const로 선언한 b 재선언 SyntaxError: Identifier 'b' has already been declared
또한 둘 다 var 와 다르게 중복해서 선언이 불가능합니다.
변수 스코프 유효범위는 둘 다 블록 레벨 스코프(block-level scope) 입니다.
함수 내부 말고도 if 나 for 문 등의 코드 블록 {...} 에서 선언된 변수도 지역 변수로 취급합니다.
일시적 사각지대(TDZ)의 발생 원인과 메모리 수집 규칙
아까 위에서 상술하였듯이 var 키워드로 선언된 변수는
선언과 동시에 메모리 상에서 undefined로 즉각 초기화가 이루어집니다.
이 때문에 실제 할당문이 적힌 라인보다 위쪽에서 변수를 호출하더라도
에러가 나지 않고 초기화된 임시 값을 정상적으로 출력해 줍니다.
반면 let과 const 키워드는
컴파일 단계에서 메모리에 이름 등록은 완료되지만 초기화 단계는 절대 진행하지 않습니다.
실제 소스 코드 상에서 let 선언문이 적힌 물리적인 위치에 도달하기 전까지
해당 변수는 메모리 주소가 비어있는 특수한 구역에 갇히게 됩니다.
이처럼 선언은 되었으나 초기화가 되지 않아 접근할 수 없는 보호 구역을
일시적 사각지대(Temporal Dead Zone, TDZ)라고 부릅니다.
구글 크롤러와 웹 브라우저 엔진은 이 TDZ 영역을 엄격하게 통제하여
개발자가 실수로 선언되지 않은 데이터에 접근하는 버그를 원천 차단합니다.
변수의 생성 단계를 '선언, 초기화, 할당'의 3단계 수명 주기로 명확히 쪼개어 이해한다면
런타임 에러 없는 견고한 웹 프로그래밍이 가능해집니다.
정적 웹 사이트 최적화 관점에서의 실행 컨텍스트 활용
자바스크립트의 실행 컨텍스트는 전역 컨텍스트와 함수 컨텍스트로 구별되며
콜 스택(Call Stack)이라는 저장소에 차곡차곡 쌓여 제어됩니다.
백엔드 서버나 복잡한 프레임워크가 없는 순수 정적 웹사이트 환경일수록
자바스크립트 파일의 용량과 실행 컨텍스트의 갯수를 최소화하는 것이 성능상 유리합니다.
불필요한 전역 변수를 무분별하게 양산하면 전역 컨텍스트의 변수 객체가 비대해져
브라우저가 메모리를 탐색하는 동선이 비효율적으로 길어집니다.
가급적 자바스크립트 코드를 작성할 때는 즉시 실행 함수(IIFE)나 모듈 구조를 활용하여
독립된 지역 실행 컨텍스트 공간으로 격리시키는 습관이 필요합니다.
이렇게 소스 코드 구조를 정돈하면 가비지 컬렉터(Garbage Collector)가
수명이 다한 컨텍스트 메모리를 즉시 수거하여 페이지 로딩 속도를 향상시킵니다.
Summary
var
• 제일 오래된 초창기 방식
• 재선언 가능, 호이스팅, 지역변수 할당방식 등 기존 매커니즘이 문제를 일으키기 쉬워서 사장되어 가는중.
let
• 최신 기준에 맞는 변수 선언 방식
• 선언 후 재선언을 막았고, 블록 기준으로 지역변수로 할당되기 때문에 문제가 적음.
const
• 최신 기준에 맞는 '상수' 선언 방식
• 선언한 값 자체는 재할당 불가능이지만, 값 내부의 인자들은 재할당 가능
• 상술한 방식으로 인해 요즘 웹에서 DOM 의 Element 제어 등에 쓰기 좋음.