[JS] 호이스팅, 스코프, var - let - const, TDZ 끝장

Devfrank·2023년 2월 9일
1

JavaScript

목록 보기
3/3
post-thumbnail

자바스크립트 단골 면접 질문 - 호이스팅, var vs let vs const에 대해서 아는 데로 설명해주시겠어요?

호이스팅(Hoisting)이란?

구글 영어사전에 따르면 호이스팅이란 밧줄과 도르래로 끌어올리다는 뜻.

: raise (something) by means of ropes and pulleys.
"high overhead great cranes hoisted girders"

Simple Korean으로 말하면, 이사짐 사다리를 사용해서 63빌딩 꼭대기 층으로 위로 올라가는 것.

aCatName("호랑이");

function aCatName(name) {
  console.log("고양이의 이름은 " + name + "입니다"); 
  // 고양이의 이름은 호랑이입니다
}

일반적으로 코드는 탑다운 방식으로 실행이 된다.
위의 예제를 보면 함수를 선언하기도 전에, 함수를 먼저 호출하는데도 문제가 없다. 콘솔이 잘 찍힌다.

debugger를 확인해보면, 실행도 전에 호이스팅을 하여 Global에 aCatName 함수가 담긴것을 볼 수 있다. 여기서 우리가 '아하'라고 생각돼야 하는 부분은, 함수가 실행도 전에 꼭대기 층으로 올라가서 어딘가에 저장된다는 것이다. 이게 호이스팅이다.

자연스럽게 우리에게 말도하지 않고, 63빌딩 꼭대기로 쭈욱 올라간다는 것.

그렇다. 호이스팅은 코드를 실행하기도 전에, 선언된 변수 및 함수를 해당 범위의 맨 위로 이동하는 JavaScript의 메커니즘이다.

함수 선언문이 아닌 함수 표현식과 화살표 함수는 호이스팅이 될까?

  • 호이스팅을 시도하지만, 함수 표현식과 화살표 함수는 다른 방법으로 define을 한다.

"The context inside arrow functions is lexically or statically defined. " -


호이스팅 var, let, const

몇 블로그 글을 보면... 누군가는 var는 호이스팅이 되는데 let과 const는 호이스팅이 되지 않는다는... 얘기를 한다. 까짓거 그럼 debugger를 사용해서, 자바스크립트 엔진에게 물어보자.

  • var 키워드

  • let, const 키워드


결론: var, let, const 코드를 실행하기도 전에, 선언된 변수 및 함수를 위로 쭈욱~~~


스코프의 반란 let, const

스코프(scope): 변수에 접근할 수 있는 범위

함수 스코프

  • 자바스크립트는 기본적으로 함수 스코프를 따르는 언어다.
  • 함수스코프를 따른다? ===> 새로운 함수가 생성될때마다 새로운 스코프가 발생한다.
  • 함수안에서 선언한 변수는 해당 함수 안에서만 접근할 수 있음.
    (함수스코프 -> 지역스코프)

function getPassword() {
	var secretKey = '12345';
}

getPassword();
console.log(secretKey); // ReferenceError - not defined

함수 외부는 Global scope이고, 함수 내부는 getPassword 함수의 scope이다. 부모 scope는 자식 scope에서 간섭할 수 없기 때문에 접근이 불가능하다.

그렇다면 var 키워드를 사용할 때, 블락{ } 에서는?

// ========= 블락 {} =========
var secretKey = "123456";

if (true) {
  var secretKey = "var changes inside block - Not safe";
}
  
console.log(secretKey); // var changes inside block - Not safe
  • 위 코드에서는 함수가 선언되어 있지 않기 때문에 새로운 환경, 새로운 스코프가 형성되지 않는다. 두꺼운 벽을 세우지 않았다.
  • 그렇기 때문에 어디에서나 secretKey에 접근이 가능하다.
  • Simple Korean - var 키워드는 함수에서만 도둑을 조심하고, 벽은 타고 집안으로 들어온다.

대표적인 예로, for loop의 블락스코프다.
블락에서 보호를 제대로 받지 못하여, 다른 스코프(범위)에서 키워드를 남발할 수 있다.

for (var password = 0; password < 2; password++){
	console.log(password); // 0, 1
}

console.log(password); // 2 
// 블락스코프 밖에서... password를 선언하지도 않았는데, i에 접근해버렸다..

var 키워드를 남발한다면... 나도 모르게 변수를.. 재사용하여.. 이거저것 해본 후... 어디선가.. 버그를 찾고 있겠지...?

Simple Korean: 도둑이 이집 저집 마음대로 넘나들면 안되니까 벽을 세워야함.

이를 위해, 도둑이 넘나들지 못하게 벽을 세우자! ES6에서 블락스코프 let, const 키워드가 등장했다.


블락 스코프 let, const 키워드

  • 원래 자바스크립트는 함수 스코프를 따르지만, let과 const 키워드의 등장으로 블락 스코프를 형성하는 것도 가능해졌다. (드디어 안전한 벽을 세울 수 있다.)
  • 블락스코프는, 블락 {}이 생성될 때마다 새로운 스코프(범위)가 형성되는 것을 의미함.
let secretKey = "Let me hack your password";

if(true){
    let secretKey = "Go away";
    console.log(secretKey); // "Go away";
  
  	const password = "*******"
  	console.log(password); // "*******"
}
  
console.log(secretKey); // Let me hack your password
console.log(password); // ReferenceError - password is not defined

let과 const를 사용하여 도둑이 집을 넘나드는것을 안전한 벽(블락)을 세우며 침입을 막는다.


선언 & 초기화 & 값 할당 & 재할당

앞서, 호이스팅를 확인하고 중요한 함수스코프와 블락스코프의 차이를 확인했다. 그렇다면 var, let, const 쓰임새는 어떻게 될까?

  • 변수는 선언 단계 -> 초기화 단계 -> 값 할당단계 3가지 단계로 나뉜다.

앞에서도 살펴봤지만, 다시 위의 사진을 보면, let과 const를 사용할때, 호이스팅이 돼서 메모리 어딘가에 undefined 로 먼저 들어가 있는 것을 확인할 수 있다. 이뿐만이 아니다, 개발자 도구에서 함수를 정의해도 undefined가 뜬다.

  • let, const는 호이스팅 때 메모리 공간만 할당하여 undefined를 넣어두려고 시도한다.

Simple Korean으로 설명을 하자면,

1. 변수 선언.
2. 초기화라는건 => 메모리 공간을 미리 만들어 둔다는 것이다. 보통 암묵적으로 'undefined'을 넣으려고함.
3. 초기화 단계에서 만들어 놓은 메모리 공간을 활용하여 => 값을 할당 및 재할당 한다.

var

  • var는 선언 & 초기화 & 값 할당을 동시에도 가능하고, 따로도 가능하다. 따로 하는 작업을 한다면, 메모리 공간을 만들어 undefined로 넣어두고 다음 테스크를 기다린다.
var whatever; // undefined
var whatever = '1' // 재선언 가능
var whatever = '2' // 재할당 가능

let

  • 호이스팅 이후 코드가 실행되며, let은 undefined에서 나중에 재할당을 할 수 있다. 즉, 선언과 초기화 분리가 가능하다.
  • 중복 선언이 안된다.
let laterPossible; // undefined
laterPossible = "changing the value"

let laterPossible = 'declared again'; // 중복선언 안됨.
// Identifier 'laterPossible' has already been declared

const

  • 호이스팅이 되며, undefined가 담기려고 노력하지만, 선언 & 초기화 & 할당이 동시에 일어나야 한다.
  • 중복선언 안됨.
  • const 만에 유니크함: => 재할당이 불가함.(예외도 있음)
  • 재할당의 경우, 원시 값은 불가능하지만, 객체는 가능하다.
  • const 키워드는 재할당을 금지할 뿐, '불변'을 의미하지 않는다.
const strictBetter; // Missing initializer in const declaration
// 1. 선언할때, 2. 초기화를 꼭 하고, 값도 할당해줘야한다.

const strictBetter = 'value in the memory' //  선언 & 초기화 & 값 할당

const strictBetter = 're-declare'; // 중복선언 안됨.
// Identifier 're-declare' has already been declared

strictBetter = 'new string'; //  Assignment to constant variable
  • 재할당 예외
const exceptionConst = {id:1, name: 'Frank'}

exceptionConst['name'] = 'Frank Kim'
exceptionConst['job'] = 'JavaScript Developer'

console.log(exceptionConst); 
// {id: 1, name: 'Frank Kim', job: 'JavaScript Developer'}

TDZ란? 사각지대

Temporal Dead Zone: => ReferenceError

(1) let

  • let 키워드로 선언한 변수는, 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 런타임 이전에 자바스크립트 엔진에 의해 선언 단계가 먼저 실행되지만, 초기화 단계가 적절하게 실행되지 않았을 때(undefined) 해당 변수에 접근하려고 하면 참조 에러가 뜬다.
console.log(name); // Uncaught ReferenceError: name is not defined
/*
......
...... T
...... D
...... Z
......
*/
let name = 'Frank'
  • 따라서 let 키워드로 선언한 변수는 스코프의 시작 지점부터 초기화 단계 시작 지점까지 변수를 참조할 수 없는 일시적 사각지대(Temporal Dead Zone: TDZ) 구간에 존재한다.

(2) const

const 키워드는 선언 단계와 초기화 단계가 동시에 진행된다.

console.log(name) 
// Uncaught ReferenceError: Cannot access 'name' before initialization

const name = 'Frank'
  • let 키워드로 선언한 경우, 런타임 이전에 선언이 되어 자바스크립트 엔진에 이미 존재하지만 초기화가 적절하게 되지 않았기 때문에, name is not defined라는 문구가 떴다.

  • 하지만 const 키워드로 선언한 경우, 선언과 초기화가 동시에 이루어져야 하지만 런타임 이전에는 실행될 수 없다. 따라서 초기화가 진행되지 않은 상태이기 때문에 Cannot access 'name' before initialization 에러 문구가 뜬다.


마무리

기본적으로 변수의 스코프는 최대한 좁게 만드는 것을 권장한다.
따라서, var > let > const 키워드를 사용하자.
변경하지 않는 값(상수)이라면 let > const 키워드를 사용하는 것이 상대적으로 안전한다.

Reference:

profile
피드백 문화를 지향합니다. Anytime please! 🙌🏻

0개의 댓글