부트캠프 <항해99> 일지/3~5주차 - 리액트 입문~심화 교육

[3주차 개인 S.A] 숙제 3 🐤 호이스팅과 TDZ는 무엇일까 ?

티레이니 2022. 5. 20. 16:06

1. 스코프, 호이스팅, TDZ

 

■ 스코프(Scope)

'범위' 라는 뜻의 영단어처럼, 스코프는 변수에 접근할 수 있는(=변수가 영향을 미치는) 유효 범위이다.

식별자(변수, 함수, 클래스)에는 접근할 수 있는 범위가 존재하며, 범위는 중괄호 { } (Block Scope)

또는 함수(Function Scope)로 인해 나눠진다. 이 범위를 스코프라고 부른다.

 

 

전역 스코프와 지역 스코프

 

전역스코프 (Global scope)

코드 어디에서든지 참조할 수 있다. 코드의 가장 바깥 영역이다.

전역 스코프에서 선언된 변수는 전역 변수(Global variable)라고 한다.

 

지역 스코프 (Local scope or Function-level scope)
코드 블록이 만든 스코프로 그 지역과 그 하위 지역에서만 참조할 수 있다.

지역 내에서 선언된 함수는 지역 함수(Local variable)가 된다.

 

 

블록 레벨 스코프와 함수 레벨 스코프

 

블록 스코프(Block Scope)

블록으로 지역이 나뉘는 스코프이다. 간단하게, { } 안에서 선언된 변수는

선언된 { } 밖에서는 사용할 수 없다. (함수, for문, if문, while문 등)

let과 const는 블록레벨 스코프를 따른다.

 

함수 스코프(Function Scope)

함수로 지역이 나뉘는 스코프이다. 자바스크립트의 var는 함수 레벨 스코프만 사용하여 

함수로는 지역이 나뉘지만 그 외의 블록에는 지역이 나뉘지 않는다 (=무시한다)

때문에 var는 if나 for문 안에서 선언되어도 외부에서 사용할 수 있다. 

이 때문에 의도치 않게 변수값이 변경되는 등 문제가 발생할 수 있어 사용을 지양한다. 

 

 

스코프의 주요 규칙

   1) 안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 반대는 불가능하다

   2) 스코프는 중첩이 가능하다

   3) 지역 변수는 전역 변수보다 우선순위가 더 높다

 

 

■ 호이스팅

 

자바스크립트는 변수와 함수의 코드를 실행하기 전에 선언에 대한 메모리부터 할당한다.

때문에, 함수를 호출하는 코드를 함수 선언보다 먼저 작성해도 동작한다.

변수도 마찬가지로 변수를 선언하기 전에 호출할 수 있다.

 

즉, 선언 단계가 스코프의 꼭대기로 "끌어올려지는" 작업이다.

 

단, 호이스팅은 '선언만을 대상으로' 한다. 

때문에 변수 선언 이전에 변수를 호출하면, 아래서 할당된 값은 불러와지지 않고

기본 초기화 상태의 값으로 나타난다.

 

(var로 선언하면 undefined, 그 외에는 초기화하지 않음)

 

let과 const의 호이스팅

let과 const로 선언한 변수도 호이스팅 대상이지만 (식별 가능),

var와 달리 호이스팅 시 undefined로 변수를 초기화하지는 않는다. 

(-> TDZ라는 특수한 영역을 사용해 참조를 방어한다)

따라서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 예외가 발생한다.

(ReferenceError: Cannot access 'variable' before initialization 발생)

 

 

■ TDZ (Temporal Dead Zone)

일시적 사각지대라는 뜻으로, 스코프의 시작부터 초기화 시작 지점까지를 TDZ 라고 한다.

변수가 초기화 되기 전(메모리가 할당되기 전)까지는 변수를 참조할 수 없다. (에러 발생)

 

var는 선언과 초기화가 동시에 이루어져 호이스팅시 undefined 값이 나타난다.

const와 let은 선언과 초기화가 나뉘어 있어서, 초기화가 되기 전까지는 참조시 에러가 뜬다.

 

 

스코프 시작 { <--         TDZ영역        -->   스코프 종료 }
변수 생성 단계 변수 선언 변수 초기화 변수 값 할당
단계별 일어나는 일 변수가 등록된다.
(실행 컨텍스트의 변수객체)
변수객체에 변수 선언을 위한 메모리가 할당된다. 메모리는 undefined로 초기화 된다. 사용자가 undefined로 초기화된 메모리에 다른 값을 할당한다.
var 변수 선언과 동시에 초기화가 이루어져 undefined 가 된다 변수에 값을 할당한다
let, const 선언만 이루어진다 선언과 분리하여 이루어진다 변수에 값을 할당한다
function 모두 동시에 이루어진다 

 

 

 

 

2. 함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

 

함수도 호이스팅 대상이다. 단, 여기서 2가지 경우를 나누어 볼 수 있다.

함수 선언식 (function declartion) :  함수명이 정의되어 있고, 별도의 할당 명령이 없는 것

함수 표현식 (function Expression) : 정의한 function을 별도의 변수에 할당하는 것

// 1. 함수 선언식
function sum(a,b) {
	return a + b;
}

// 2. 함수 표현식
const minus = function (a,b) {
	return a - b;
}

함수 선언식은 함수 전체를 호이스팅 한다.

정의된 범위의 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있다.

함수 표현식은 별도의 변수에 할당하게 되는데, 

변수는 선언부와 할당부를 나누어 호이스팅 하게 된다.

여기서 선언부만 호이스팅하게 된다.

( -> 위 예시에서는 const로 선언되었기 때문에, TDZ가 발생한다)

 

즉, 위 예시 코드에서 첫번째 줄 위에 sum과 minus 함수를 각각 호출한다면,

sum은 정상 작동되고 minus는 참조오류가 발생할 것이다 :)

 

 

 

 

3. let, const, var, function의 실행 원리 정리

■ Var 

1) 변수선언과 동시에 초기화가 함께 이루어져 undefined 값이 할당된다.

2) 함수 레벨 스코프를 사용하여 함수 외의 코드블록으로는 지역변수가 되지 않는다

3) 동일한 이름으로 여러번 중복 선언이 가능하며, 이 경우 마지막으로 할당된 값이 변수에 저장된다.

4) 변수 선언 및 초기화 이후 반복해서 다른 값을 재할당 할 수 있다.

 

■ Let

1) 변수 선언과 초기화가 분리되어 호이스팅시 참조위치를 찾을 수 없는 에러가 발생한다

2) 블록 레벨 스코프를 사용하여 { } 안의 변수는 모두 해당 블록의 지역 변수가 된다.

3) 중복선언이 불가능하며, 시도하면 해당 변수가 이미 선언되었다는 에러가 뜬다

4) 변수 선언 및 초기화 이후 반복해서 다른 값을 재할당 할 수 있다.

 

■ Const

1) 변수 선언과 초기화가 분리되어 호이스팅시 참조위치를 찾을 수 없는 에러가 발생한다

2) 블록 레벨 스코프를 사용하여 { } 안의 변수는 모두 해당 블록의 지역 변수가 된다.

3) 중복선언이 불가능하며, 시도하면 해당 변수가 이미 선언되었다는 에러가 뜬다

4) 변수를 선언하여 한번 값을 할당하면 다른 값을 재할당 할 수 없다. 
** const에 할당된것이 배열과 객체 등 참조형 데이터라면, 다른 값을 재할당할 수는 없지만

배열과 객체 내부의 데이터를 변경하는 것은 얼마든지 가능하다. 할당된 내용에는 주소값만 들어있기 때문이다.

 

■ Function 

1) 선언과 초기화, 값 할당이 동시에 이루어진다.

2) 호이스팅 대상으로, 선언보다 앞에서 호출해도 정상적으로 불려진다. (함수 표현식의 경우에는 X)

 

 

 

 

4. 실행 컨텍스트와 콜 스택

 

■ 실행컨택스트 (Execution Context)

자바스크립트에서  코드를 실행하기 위해 필요한 환경 = 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.

자바스크립트가 왜 그렇게 동작하는 지 보여주며, 모든 코드는 특정한 실행 컨텍스트 안에서 실행된다.

 

작동 방식 :

1) ​코드가 실행 (브라우저가 스크립트를 로딩해서 실행) 되는 순간, 모든 것을 포함하는 전역 컨텍스트가 생기고,

페이지가 종료될까지 유지된다.


2) 또 함수를 호출할 때마다 함수 컨텍스트가 하나씩 더 생긴다. 


3) 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성 된다.

 

4) 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾는다. ( 상위 스코프로 타고 올라가며 찾는다! )​​

 

5) 함수가 실행되는 동안에는 전역컨텍스트는 멈춘다.  (왜나면 자바스크립트는 싱글 스레드 환경이기 때문이다.

= 한번에 한코드씩만 실행가능하다.)

 

6) 함수 실행이 마무리되면 해당 컨텍스트는 (클로저 제외) 사라지며, 페이지가 종료되면 전역 컨텍스트가 사라진다.

 

전역 컨텍스트 (Global Execution Context)

디폴트 실행 컨텍스트로, 자바스크립트 파일이 엔진에 의해 처음 로드되었을 때 실행되기 시작하는 환경이다.

전역 컨텍스트가 생성된 후 변수객체, scope chain, this가 들어온다. 

전역 컨텍스트는 arguments(함수의 인자)가 없다. variable은 해당 스코프의 변수들 이다.

 

 함수 컨텍스트 (Fuction Execution Context)

우리가 execution context를 따로 구성하는 방법은 함수를 실행하는 것 뿐이다. 함수가 호출되고 실행됨에 따라서 해당 함수 안에서 생성되는 컨텍스트. 각각의 함수는 고유의 실행 컨텍스트를 가진다. 그리고 전역 실행 컨텍스트에 언제나 접근할 수 있다.

 

실행 컨택스트가 수집하는 정보들

1. Variable Environment: 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보. 항상 값을 유지한다.

2. Lexical Environment : 백과사전 같은 개념. Variable Environment에서 복사해온다. 일시적으로 변동되기도 함.

"현재 컨텍스트의 내부에는 a,b와 같은 식별자들이 있고 외부 정보는 D를 참조하도록 구성되어 있다."라는 환경 정보들

3. Outer Environment Reference : 중첩 유효 범위를 가질 수 있는 환경에서 상위 Lexical Environment를 참조한다.
즉, 외부 환경에 대한 참조를 가지고 있다. 전역 환경에서는 null이다.

4. Environment Record 유효범위 내의 값에 식별자를 매핑한다. 변수 선언 및 함수 선언을 저장한다.

 

콜 스택 (callstack)

call은 호출을 뜻한다. stack은 출입구가 하나뿐인 깊은 우물 같은 데이터 구조다.

따라서 callstack은 자바스크립트가 함수 호출을 기록하기 위해 사용하는 우물 형태의 데이터 구조이다.

항상 맨 위에 놓인 함수를 우선으로 실행된다. 이런 식으로 자바스크립트 엔진은 가장 위에 쌓여있는 context와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.

 

5. 스코프 체인, 변수 은닉화

 

■ 스코프 체인

스코프 체인은 해당 코드의 유효 범위(in scope) 안에 있는 변수를 정의하는 객체의 체인, 리스트다.

지역 변수를 어떤 객체의 프로퍼티로 생각한다면, 자바스크립트의 모든 코드는 스코프 체인을 갖고 있다.

 

자바스크립트가 변수 값을 얻으려고 할 때(variable resolution, 변수 해석) 스코프 체인에서 변수를 찾는다. 스코프 체인은 위에서 말했다시피 객체의 리스트이므로, 첫 번째 객체에서 해당 변수를 찾고, 없으면 그 다음 객체에서 해당 변수를 찾고, 여기도 없으면 그 다음 객체에서 찾는 식이다. 리스트의 끝까지 탐색했는데도 그 변수가 없다면 reference error가 발생하는 것이다. 최상위 자바스크립트 코드(어떠한 함수에도 속하지 않는 코드)의 스코프 체인에는 하나의 객체만 있고, 그것이 전역 객체이다. 


함수가 정의될 때, 함수는 스코프 체인을 저장한다.
함수가 호출될 때, 함수는 지역 변수를 보관하는 새로운 객체를 만들고 그 객체를 기존에 만들어둔 스코프 체인에 추가한다.

 

■ 변수 은닉화 / 캡슐화 / 정보은닉
어떠한 필드를 외부에서 사용할 수 없게끔 숨기는 것으로,

잘못 사용될 수 있는 객체의 특정 부분을 사용자가 사용할 수 없게 막는 기술이다.

 

객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶고
실제 구현 내용 일부를 외부에 감추어 은닉한다.

 

간단하게 봤을 때, function1 안에 function2을 넣어두면

그 안의 변수는 외부에서 직접적으로 접근 할 수 없다.

오로지 미리 할당된 함수로만 접근하고 사용할 수 있다.

캡슐화 (은닉화) 되었기 때문이다.

 

이런식으로 손댈수 없게 꽁꽁 감춰두어서 의도치 않게

데이터가 변경되는 일을 막아버린다.

 

 

■  클로저?

.. 찾아보는데 계속 말이 어렵다.

클로저의 개념이 계속 같이 나오는데..

클로저는 더 이해하기 힘들었다..

 

일단 클로저를 이해할 수 있는 선에서 정리하면

클로저는 함수 + 함수를 둘러싼 환경 이라고도 하고

생성될 당시의 환경을 기억하는 함수라고도 하는데..

 

위쪽에 4번 실행컨텍스트에서

  • 함수를 호출할 때마다 함수 컨텍스트가 하나씩 생긴다.
  • 각각의 함수는 고유의 실행 컨텍스트를 가진다.
  • 실행 컨텍스트 = 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다 

라고 했으니,

 

함수 컨텍스트를 기억하는 함수? 인가보다.

특수한 점은, 복사본(Lexical Environment) 이라는 것?

 

스코프 내의 영역이 소멸(remove) 되었어도 

그에 대한 접근(access)은 독립된 복사본인 클로저를 통해 이루어질 수 있다.

 

... 며칠 더 지나면 이해가 될까?

 

관련 포스트 https://meetup.toast.com/posts/86

 

자바스크립트의 스코프와 클로저 : NHN Cloud Meetup

자바스크립트의 스코프와 클로저

meetup.toast.com