Skip to content

호출스택, 호이스팅, 스코프, 클로저에 관한 최고의 가이드

Dia edited this page Apr 2, 2019 · 1 revision

Javascript 를 이해하는데 있어 가장 중요한 기본적인 개념은 실행 컨텍스트를 이해하는 것 입니다. 실행 컨텍스트를 학습한다면 호이스팅, 스코프체인 등과 같은 고급 기술을 익힐 수 있습니다. 소프트웨어를 작성하는 한 가지 전략은 코드를 각각의 조각으로 분리하는 것입니다. 분리된 "조각" 은 함수, 모듈, 패키지등 각각의 이름이 있고, 단일 목적을 가지고 있습니다. 이 "조각"은 프로그램의 복잡성을 낮추고 관리를 쉽게합니다. 실행컨텍스트는 함수, 모듈, 패키지와 마찬가지로 코드를 관리하여 Javascript 엔진이 코드의 복잡성을 낮출수 있도록 합니다.

Javascript 엔진이 코드를 실행할 때 생성되는 첫번째 실행 컨텍스트를 "전역 실행 컨텍스트" 라고합니다. 이 실행 컨텍스트에는 전역 객체와 this 변수 두가지로 구성되어있습니다. this 는 브라우저에서 실행될 경우 window 를 참조하고, Node 환경에서 실행중인경우 global 객체를 참조합니다.

이미지1

기본적인 실행 컨텍스트 구성은 window 와 this 두 가지로 되어 있습니다. 프로그램에 몇가지 변수를 추가할 때 어떤 일이 일어나는지 보겠습니다.

이미지2 이미지3

실행 컨텍스트는 Creation 단계와 Execution 분리된 두 단계를 가지며 단계마다 책임을 가지고 있습니다.

Creation 단계에서는

  1. 전역 객체를 만듭니다.
  2. 'this' 라 불리는 객체를 만듭니다.
  3. 변수와 함수를 위한 메모리 공간을 만듭니다.
  4. 함수 선언을 메모리에 배치하는 동안 변수에 'undefined' 를 할당합니다.

아래의 GIF 에서 Creation 에서 Execution 의 흐름을 볼 수 있습니다.

이미지4

Creation 에서 window 와 this 가 만들어지고 변수 (name, handle) 에 default 값인 undefined 가 할당되고, 함수 (getUser) 가 메모리에 배치됩니다. Execution 단계에서는 코드가 한줄씩 실행하기 시작되며 메모리에 저장된 변수들에 실제 값을 할당합니다.

GIF 도 멋지지만 단계별 코드 프로세스를 보는 것 만큼 멋지지 않습니다. JavaScript Visualizer 코드를 확인하세요. 예제의 정확한 코드를 보기 원한다면 여기 를 확인해주세요

CreationExecution 개념을 비교하기위해 Creation 후와 Execution 전에 몇 가지 값을 기록해보겠습니다.

console.log('name: ', name)
console.log('handle: ', handle)
console.log('getUser :', getUser)

var name = 'Tyler'
var handle = '@tylermcginnis'

function getUser () {
  return {
    name: name,
    handle: handle
  }
}

위의 콘솔들에 어떤 내용이 출력 될까요? Javascript 엔진에서 한 줄씩 코드를 실행하며 console.log 를 호출할때 이미 Creation 단계가 실행되었습니다. 이전에 봤듯이 변수들에는 undefined 가 할당이 되어있고, 함수들은 메모리에 배치됩니다. 우리가 기대한대로 name 와 handle 에는 undefined 가 할당되어 졌고 getUser 는 메모리에 function 으로 참조되어집니다.

console.log('name: ', name) // name: undefined
console.log('handle: ', handle) // handle: undefined
console.log('getUser :', getUser) // getUser: ƒ getUser () {}

var name = 'Tyler'
var handle = '@tylermcginnis'

function getUser () {
  return {
    name: name,
    handle: handle
  }
}

Creation 단계에서 변수를 기본값으로 지정하는것을 호이스팅 이라고 합니다.

호이스팅에 대해 혼란스러운 것은 실제로 끌어올리는 것이나 움직이는 것이 없다는 것입니다.

함수 실행 컨텍스트는 글로벌 실행 컨텍스트와 비슷하며 함수가 호출 될 때마다 생성됩니다.

실행 컨텍스트가 작성되는 유일한 시점은 Javascript 엔진이 처음 코드를 해석하고 함수가 호출 될 때 입니다.

주요 질문은 전역 실행 컨텍스트와 함수 실행 컨텍스트의 차이입니다. 이전의 살펴본 글로벌 Creation 단계에서는 Javascript 엔진이

  1. 전역 객체를 만듭니다.
  2. 'this' 라 불리는 객체를 만듭니다.
  3. 변수와 함수를 위한 메모리 공간을 만듭니다.
  4. 함수 선언을 메모리에 배치하는 동안 변수에 'undefined' 를 할당합니다.

함수 실행 컨텍스트와 글로벌 실행 컨텍스트와의 차이점이 있는 단계는? 1단계입니다. 함수가 호출되고 JavaScript 엔진이 함수 실행 컨텍스트를 생성할 때마다 전역 실행 컨텍스트의 Creation 단계에서 생성된 하나의 글로벌 객체만 있어야 합니다. 함수 실행 컨텍스트가 생성될 때마다 JavaScript 엔진은

  1. 인수를 만듭니다.
  2. 'this' 라 불리는 객체를 만듭니다.
  3. 변수와 함수를 위한 메모리 공간을 만듭니다.
  4. 함수 선언을 메모리에 배치하는 동안 변수에 'undefined' 를 할당합니다.

실제로 보기위해서 이전 코드로 돌아가야합니다. 이번에는 정의 대신 getUser 를 호출 할 때 어떤일이 일어나는지 보겠습니다.

Visualize

이미지5

우리가 이야기 했던 것처럼 getUser 를 호출 할 때 컨텍스트가 만들어집니다. getUsers 컨텍스트를 생성할 때 Javascript 엔진은 this 객체 뿐만 아니라 arguments 객체를 생성합니다. getUser에는 변수가 없기 때문에 JavaScript 엔진은 메모리 공간을 설정하거나 변수 선언을 호이스팅할 필요가 없습니다.

getUser 함수 실행이 완료되면 제거되어집니다. Javascript 엔진은 실행스택(호출 스택이라고도 부름) 을 만듭니다. 함수가 호출 될 때마다 새로운 실행 컨텍스트가 만들어지며 실행 스택에 추가됩니다. 함수가 CreationExecution 단계를 마칠때마다 실행 스택에서 꺼내어집니다. Javascript 는 단일 스레드로 구성되어져 있기 때문에 (한번에 하나의 작업만 가능하다) 시각화하기 쉽습니다.

Visualize

이미지6

함수가 지역변수를 가지도록 코드를 수정해보겠습니다.

Visualize

이미지7

주목해야할 세부사항은 거의 없습니다. 첫째 사용자가 입력한 모든 argument 는 해당 함수의 실행 컨텍스트에 로컬 변수로 할당됩니다. 예제의 handle 은 글로벌 실행 컨텍스트에 변수로 존재하며, getURL 에 인수로 전달했기 때문에 getURL 실행 컨텍스트에도 존재합니다. 다음은 함수의 내부에 선언된 변수들이 해당 함수의 실행 컨텍스트 안에 살아있다는 것 입니다. TwitterURL 는 글로벌 실행 컨텍스트가 아니라 getURL 실행 컨텍스트 내에 존재 합니다. 당연해 보일 수 있지만 이것은 다음 주제인 스코프에 대한 것입니다.

"변수에 접근할 수 있는 공간" 스코프에 대한 이야기를 들어봤을 것 입니다. 실행 컨텍스트와 JavaScript Visualizer 도구를 이용하면 스코프에 대한 지식은 명확해질 것 입니다. 실제 MDN 에서는 스코프를 지금 실행중인 컨텍스트라고 정의합니다. "변수에 접근할 수 있는 공간"을 우리가 지금까지 실행해왔던 맥락이랑 유사한 방법으로 생각할 수 있습니다.

아래의 코드는 뭐라고 기록될까요 ?

function foo () {
  var bar = 'Declared in foo'
}

foo()

console.log(bar)

Visualize

이미지8

foo 함수가 호출되면 실행 스택에 새로운 실행 컨텍스트를 생성합니다. 생성 단계에서는 arguments 와 bar 를 undefined 로 설정합니다. 그 다음 Execution 단계에서 foo 에 선언된 string 을 bar 에 할당합니다. 그 후 Execution 단계가 종료되고 foo 실행 컨텍스트가 스택에서 나옵니다. 실행 스택에서 foo 가 제거되면 콘솔에 bar 기록을 시도합니다. 이것이 우리에게 보여주는 것은 함수 내부에서 생성된 변수들의 범위가 정해져 있다는 것 입니다. 실행 컨텍스트가 실행 스택에서 나온 후에는 접근 할 수 없습니다.

다른 코드를 보겠습니다. 콘솔에 기록될 내용은 무엇일까요 ?

function first () {
  var name = 'Jordyn'

  console.log(name)
}

function second () {
  var name = 'Jake'

  console.log(name)
}

console.log(name)
var name = 'Tyler'
first()
second()
console.log(name)

Visualize

이미지9

위의 코드를 통하여 알 수 있는 것은 각각의 새로운 실행 컨텍스트에 고유한 변수 환경을 가지고 있다는 것입니다. 변수가 포함되어 있는 실행 컨텍스트가 있더라도 Javascript 엔진은 먼저 해당 변수에 대한 실행 컨텍스트를 찾습니다.

var name = 'Tyler'

function logName () {
  console.log(name)
}

logName()

Visualize

이미지10

Javascript 엔진이 함수 실행 컨텍스트에 대한 로컬 변수를 찾지 못하면 변수에서 가까운 부모 실행컨텍스트를 찾습니다. 이것을 스코프 체인이라고하는데 이 체인은 전역 실행 컨텍스트까지 계속 됩니다. 전역 실행 컨텍스트에도 없다면 참조 오류가 발생합니다.

하위 실행 컨텍스트는 부모 실행 컨텍스트를 참조할 수 있지만 부모는 하위 컨텍스트를 참조할 수 없습니다.

Visualize

이미지11

makeAdder 가 실행 컨텍스트에서 나온후 JavaScript Visualizer 는 Closure Scope 라는 것을 만듭니다. 다른 함수 내부에서 내포된 함수를 가지고 있기 때문에 Closure Scope 안에는 makeAdder 실행 컨텍스트와 동일한 환경이 있습니다. makeAdder 는 Closure 를 생성합니다. Closure 가 실행 스택에서 나온 후에도 Closure Scope 를 통해 내부 변수에 접근할 수 있습니다.

Bonus Section

몇 가지 더 관련된 주제가 있습니다.

전역 변수

전역 실행 컨텍스트에 변수를 만들면 window 객체의 속성으로 추가됩니다.

Node 의 경우 전역 객체의 속성으로 추가됩니다.

// In the browser
var name = 'Tyler'

function foo () {
  bar = 'Created in foo without declaration'
}

foo()

console.log(window.name) // Tyler
console.log(window.bar) // Created in foo without declaration

let const 동영상

this

Clone this wiki locally