프로필사진
움짤로 보는 자바스크립트 동작 원리(3) - Scope (Chain)

2020. 5. 14. 18:56🔴 FE/JavaScript

300x250

원문 (영어) : https://dev.to/lydiahallie/javascript-visualized-hoisting-478h
저자 : Lydia Hallie

👩🏻‍💻Thanks to Lydia for this amazing article about Javascript!


아래의 코드를 보자 :

const name = "Lydia"
const age = 21
const city = "San Francisco"


function getPersonInfo() {
  const name = "Sarah"
  const age = 22

  return `${name} is ${age} and lives in ${city}`
}

console.log(getPersonInfo())

우리는 name, age, city 변수들의 값을 string 형태로 리턴하는 getPersonInfo 함수를 호출한다 : 
Sarah is 22 and lives in San Francisco.
하지만, getPersonInfo 함수는 city라는 변수를 가지고 있지 않다. 어떻게 city의 값을 안 것일까?

먼저, 메모리 공간은 컨텍스트에 따라 세팅이 된다.
우리는 getPersonInfo함수의 디폴트 global context (브라우저에서는 window, 노드에서는 global)와 local context를 가지고 있고, 각 context마다 scope chain을 지닌다.

getPersonInfo함수는 이런 형태의 scope chain을 가지고 있다 :

scope chain은 기본적으로 객체들에 대한 "참조들의 체인(chain of references)"이다.
객체들은 해당 실행 컨텍스트 안에서 참조 가능한 값들(혹은 다른 스코프)에 대한 참조들을 가지고 있다.
(= "이봐, 이것들은 당신이 이 컨텍스트 안에서 참조 가능한 값들이야!")
scope chain은 실행 컨텍스트가 만들어질 때 생성되는데, 즉 런타임에서 만들어진다는 얘기다.

하지만 저자는 본 포스트에서 activation object나 실행 컨텍스트에 대해 말하지 않겠다. 오로지 scope에 집중하자!
아래 예시들에서 실행 컨텍스트 내부의 키/값 쌍은 scope chain이 변수들을 참조하는 것을 나타낸다.

전역 실행 컨텍스트에서 scope chain은 3개의 변수를 참조하고 있다 :
name - Lydia, age - 21, city - San Francisco

지역 컨텍스트에서는 2개의 변수를 참조하고 있다 :
name - Sarah, age - 22

우리가 getPersonInfo함수 안에 있는 변수들을 접근할 때, 엔진은 먼저 지역 scope chain을 체크한다.

지역 scope chain은 name과 age를 참조한다.
그런데 만약 city 변수에 접근하면 어떤 일이 발생할까?

city의 값을 찾기 위해 엔진은 "scope chain을 따라 내려간다".
이는 기본적으로 엔진이 쉽게 포기하지 않는다는 것을 뜻한다.
엔진은 city 변수의 값을 찾기위해 지역 scope가 참조하고 있는 바깥 scope (여기서는 전역 객체를 가리킴)까지 살펴볼 것이다.

전역 컨텍스트에서 우리는 city 변수를 San Francisco로 선언했다.
따라서, city 변수에 대한 참조를 가지고 있다.
우리는 해당 변수에 대한 값을 가지고 있기 때문에, getPersonInfo함수는 아래의 string 결과를 리턴할 수 있다.

"Sarah is 22 and lives in San Francisco"


우리는 scope chain을 따라 내려갈 수는 있지만, 올라가는 것은 불가능하다.
어떤 이들은 내려간다는 것을 올라간다고 표현해서 혼동이 올 수 있으니 다시 정리를 하자면 :

우리는 바깥 scope으로 갈 수 있지만, 안쪽의 scope로는 갈 수 없다.
이것을 폭포와 같은 형태로 볼 수 있다.

더 깊게 표현하자면 :


아래의 코드를 예시로 살펴보자

거의 비슷하지만, 한 가지 큰 차이가 있다 : 
우리는 city만 getPersonInfo 함수에 선언했고, 전역 scope에는 선언하지 않았다.
우리는 아직 getPersonInfo 함수를 호출하지 않았기 때문에 지역 콘텍스트가 아직 생성되지 않았다.
그러나 우리는 전역 콘텍스트에서 name, age, city의 값들에 접근해보려고 한다.

ReferenceError을 뱉어낸다!
이는 전역 scope에서 city라는 변수에 대한 참조를 찾을 수 없고,
바깥 scope가 존재하지 않기 때문에 scope chain을 따라 위로 올라갈 수 없다.

이러한 방법으로 우리는 scope을 변수를 "보호"하기 위한 용도로 사용할 수 있으며, 변수의 이름을 재활용할 수 있다.


전역, 로컬 scope 외에 블록 scope라는 것이 있다.
let이나 const로 선언된 변수들은 가장 가까운 중괄호({})로 범위가 정해진다.

const age = 21

function checkAge() {
  if (age < 21) {
    const message = "You cannot drink!"
    return message
  } else {
    const message = "You can drink!"
    return message
  }
} 

여기에 있는 scope들을 이렇게 시각화할 수 있다 :

전역 scope 1개, 함수 scope 1개, 그리고 블록 scope 2개가 있다.
message라는 변수는 중괄호에 의해 범위가 정해져 있기 때문에 우리는 해당 변수를 두번 선언할 수 있다.


Quick Recap

- scope chain = 현재 컨텍스트 내에서 우리가 접근할 수 있는 값에 대한 참조들의 chain 형태

- scope chain을 따라 내려가는 것만 가능해서 우리는 scope chain 아래에서 선언한 변수의 이름을 재활용할 수 있다.

300x250