5. 실행 컨텍스트와 클로저 #3 스코프 체인

2021. 11. 8. 13:12JavaScript/basic

이전글

https://yonghwankim-dev.tistory.com/162

 

5. 실행 컨텍스트와 클로저 #2 실행 컨텍스트 생성 과정

이전글 https://yonghwankim-dev.tistory.com/161 5. 실행 컨텍스트와 클로저 #1 실행 컨텍스트 개념 이전글 https://yonghwankim-dev.tistory.com/159 4. 함수와 프로토타입 체이닝 #5 프로토타입 체이닝 이전글..

yonghwankim-dev.tistory.com

 

본 글은 INSIDE JAVASCRIPT 도서의 내용을 복습하기 위해 작성된 글입니다.

 

요약 및 정리

  1. 전역 실행 컨텍스트의 스코프 체인
    • 젼역 실행 컨텍스트 생성할 때 스코프 체인은 자기 자신만을 가짐
  2. 함수를 호출한 경우 생성되는 실행 컨텍스트의 스코프 체인
    • 각 함수 객체는 [[scope]] 프로퍼티로 현재 컨텍스트의 스코프 체인을 참조
    • 현재 실행되는 함수 객체의 [[scope]] 프로퍼티를 복사하고, 새롭게 생성된 변수 객체를 해당 체인의 제일 앞에 추가
    • 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인

 

개요

실행 컨텍스트 생성 과정에서 생성된 스코프 체인이 어떻게 만들어지고 구성되는지 살펴보겠습니다. 

 

자바스크립도 다른 언어와 마찬가지로 스코프, 즉 유효 범위가 존재합니다. 이 유효 범위 안에서 변수와 함수가 존재합니다. 아래 코드는 C언어의 유효 범위를 확인할 수 있는 코드입니다.

void example_scope()
{
    int i = 0;
    int value = 1;
    for(i=0; i<10; i++){
    	int a = 10;
	}
    printf("a : %d", a);	// 컴파일 에러
    
    if(i==10){
    	int b = 20;
    }
    printf("b : %d", b);	// 컴파일 에러
    printf("value : %d", value);	// 1
}

C언어의 스코프(유효 범위)는 함수의 블록뿐만 아니라 if, for 문의 블록안에 선언된 변수가 밖에서는 접근이 불가능합니다. 하지만 자바스크립트에서는 함수의 블록만이 유효 범위입니다. 이 유효 범위를 나타내는 스코프가 [[scope]] 프로퍼티로 각 함수 객체 내에서 연결 리스트 형식으로 관리되는데, 이를 스코프 체인이라고 합니다.

 

각각의 함수는 [[scope]] 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조합니다. 함수가 실해오디는 순간 실행 컨텍스트가 생성되고, 이 실행 컨텍스트는 실행된 함수의 [[scope]] 프로퍼티를 기반으로 새로운 스코프 체인을 생성합니다.

 

1. 전역 실행 컨텍스트의 스코프 체인

var var1 = 1;
var var2 = 2;
console.log(var1);  // Expected Output : 1
console.log(var2);  // Expected Output : 2

위의 코드는 전역 코드입니다. 함수가 선언되지 않아 함수 호출이 없고 실행 가능한 코드들만 나열되어 있습니다. 위 자바스크립트 코드를 실행하면, 먼저 전역 실행 컨텍스트가 생성되고 변수 객체가 생성됩니다. 이 변수 객체의 스코프 체인은 자기 자신만을 가집니다. 즉, 변수 객체의 [[scope]]는 변수 객체 자신을 가리킵니다.

 

 

2. 함수를 호출한 경우 생성되는 실행 컨텍스트의 스코프 체인

var var1 = 1;
var var2 = 2;

function func(){
    var var1 = 10;
    var var2 = 20;

    console.log(var1);      // Expected Output : 10
    console.log(var2);      // Expected Output : 20
}

func();
console.log(var1);  // Expected Output : 1
console.log(var2);  // Expected Output : 2

위 예제를 수행하면 전역 실행 컨텍스트가 생성되고 func() 함수 객체가 생성됩니다. func() 함수 객체의 스코프 체인은 아래 그림과 같습니다.

위 그림처럼 func 컨텍스트의 스코프 체인은 실행된 함수의 [[scope]] 프로퍼티를 그대로 복사 후, 현재 생성된 변수 객체를 복사한 스코프 체인의 맨 앞에 추가합니다. func 실행 컨텍스트의 스코프 체인은 위의 그림과 같이 [func 변수 객체 - 전역 객체]가 된다. 위 그림을 바탕으로 스코프 체인을 정리하면 아래와 같습니다.

  • 각 함수 객체는 [[scope]] 프로퍼티로 현재 컨텍스트의 스코프 체인을 참조됩니다.
  • 한 함수가 실행되면 새로운 실행 컨텍스트가 생성되는데, 이 새로운 실행 컨텍스트는 자신이 사용할 스코프 체인을 다음과 같은 방법으로 생성합니다. 현재 실행되는 함수 객체의 [[scope]] 프로퍼티를 복사하고, 새롭게 생성된 변수 객체를 해당 체인의 제일 앞에 추가합니다.
  • 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인

내부 함수에 따른 실행 컨텍스트 생성

var value = "value1";

function printFunc(){
    var value = "value2";

    function printValue(){
        return value;
    }
    console.log(printValue());      // Expected Output : value2
}

printFunc();

위의 코드를 그림으로 표현하면 아래와 같습니다.

 

렉시컬 스코프(Lexical Scope)에 따른 스코프 체인 구성

var value = "value1";

function printValue(){
    return value;
}

function printFunc(func){
    var value = "value2";

    console.log(func());      // Expected Output : value1
}

printFunc(printValue);

위 코드를 보면 전역 ->printFunc() ->printValue() 순으로 함수가 실행되는 것을 볼 수 있습니다. 주목할 점은 이전 예제는 함수안의 함수인 내부 함수에서 값을 참조하는 것이었는데, 이번 예제는 printFunc() 함수가 printValue() 함수를 호출하였는데 printValue() 함수의 위치가 printFunc() 함수와 동일하다는 점입니다. 이렇게 될 경우 스코프 체인의 구성은 아래 그림과 같습니다.

printValue 실행 컨텍스트 변수 객체의 스코프 체인에서 2 : printFunc 변수 객체가 스택으로 쌓이지 않은 이유는 자바스크립트는 렉시컬 스코프(Lexical Scope)를 따르기 때문입니다.

 

렉시컬 스코프란 무엇인가?

렉시컬 스코프란 함수가 어디서 선언되었는지에 따라 상위 스코프를 결정하는 것입니다. 위의 예제에서 printValue 함수의 위치가 전역 코드 바로 아래에 있었기 때문에 printFunc 함수와 printValue 함수의 상위 스코프는 전역 객체가 되는 것입니다. 렉시컬 스코프와는 반대로 동적 스코프(Dynamic Scope)도 존재합니다.

 

동적 스코프란 무엇인가?

동적 스코프란 함수가 어디서 호출되었는지에 따라 상위 스코프를 결정합니다. 만약 자바스크립트가 동적 스코프라면 위의 예제에서 printFunc 함수에서 printValue 함수를 호출할때 printValue 컨텍스트의 변수 객체에서 스코프 체인은 2: printFunc 변수 객체가 스택형식으로 쌓였을 것입니다.

 

References

source code : https://github.com/yonghwankim-dev/javascript_study
INSIDE JAVASCRIPT 한빛미디어, 송형주 저