호이스팅, 스코프 체인 그리고 실행컨텍스트

    반응형

    const let var

    실행컨텍스트를 통해 배우는 호이스팅, 스코프 체인, 클로저


    1. 실행컨텍스트

    실행컨텍스트를 통해 배우는 것이니까 실행컨텍스트가 무엇인지부터 알아야겠죠? 먼저 실행컨텍스트의 정의를 알아보면 코드를 실행하는데 필요한 환경을 제공하는 객체입니다.

     

    사실 언제나 늘 그렇듯 정의만 들어서는 잘모르겠는게 당연합니다. 실행컨텍스트는 보다 효율적인 식별자 결정을 하기 위해 사용하는데요. 식별자 결정? 역시 생소한 언어입니다. 실행컨텍스트의 생성시가와 삭제되는 시기에 대해 알아보면서 시작해보겠습니다.

     

    자바스크립트 코드를 실행시키면 자바스크립트 엔진은 콜 스택이라는 통에 전역 실행컨텍스트를 담습니다. 이 실행 컨텍스트 내부에는 Record와 Outer가 담겨있습니다. 사실 정확한 명칭으로는 Environment Record와 Outer Environment Reference입니다. 환경 레코드와 외부 환경 참조이고, 이 둘을 합쳐서 Lexical Environment, 렉시컬 환경이라고 부릅니다.

     

    다시 돌아와 지금 콜 스택에는 전역 컨텍스트가 생성되어있습니다. 이 때 전역에서 함수A를 실행시켰다고 하면, 함수A의 실행 컨텍스트를 생성해서 콜 스택에 담습니다. 또 함수A 안에 있는 함수B를 실행시키면 마찬가지로 함수B 실행 컨텍스트를 생성해서 콜 스택에 담습니다.

     

    스택의 특성이 바로 쌓아올리는 것인데요. 콜 스택 안에 제일 밑에 전역 컨텍스트 그 위에 함수A 컨텍스트, 함수B 컨텍스트가 쌓이게됩니다. 이렇게 쌓인 컨텍스트들은 순차적으로 함수B가 마무리되면 함수B 컨텍스트가 사라지게됩니다. 이후 함수A의 실행이 끝나도 역시 동일하게 동작하고, 전역에 있는 코드까지 마무리가 된다면 전역 실행컨텍스트까지 사라지게 됩니다.

     

    2. Record로 이해하는 호이스팅

    실행 컨텍스트가 생성이 되고, 사라지는 과정을 간략히 살펴보았습니다. 이제 위에서 말했던 환경 레코드 줄여서 Record라고 부르겠습니다. 이 Record 안에는 어떤 것들이 들어가 있게될까요? 이름을 살펴보면 Record 무언가를 기록하는 곳이라 예상할 수 있습니다.

     

    이제 이쯤에서 호이스팅 현상에 대해 한 번 간략히 살펴볼건데요. 호이스팅은 변수 호이스팅과 함수 호이스팅으로 나뉘게 되는데 먼저 변수 호이스팅에 대해 알아보겠습니다.

     

    예를 들어 var 키워드로 favoriteFood라는 변수를 선언하고 값은 strawberry로 설정했다고 가정해보겠습니다. 이 때 선언하기 이전에 변수를 콘솔로 찍어보게 된다면 어떤일이 일어나게 될까요? strawberry가 뜰까요? 에러가 나오게 될까요? 정답은 undefined를 반환해줍니다. 마치 변수사 선언이 되고 값이 할당이 안된것처럼 동작하는데요. 이렇게 선언문이 최상단에 끌어올려진 듯한 현상을 호이스팅 현상이라고 얘기를 하게됩니다. 이러한 현상이 어떻게 이뤄지는 Record를 통해 좀 더 자세히 알아보겠습니다.

     

    자바스크립트 엔진은 자바스크립트 코드를 읽으면서 변수와 같은 정보를 실행컨텍스트 내부 Record라는 곳에 기록해둡니다. 이 Record라는 곳은 식별자와 식별자에 바인딩된 값을 기록해두는 객체입니다.

     

    위의 예시를 이제 더 자세히 살펴보겠습니다. 자바스크립트 엔진은 자바스크립트 코드를 만나면서 전역 컨텍스트를 생성하게됩니다. 이후 선언할 것이 있는지(변수나 함수 선언문이 있는지) 살펴보고 있다면 먼저 선언해두게 됩니다. 이 선언해둔 것들을 바로 Record 안에 기록하게 되는 것이죠. 전역 컨텍스트를 생성한 뒤 선언할 것이 있는지 한 번 훑어봅니다. 이 때 var 키워드로 선언한 favoriteFood라는 Record 안에 기록하게됩니다. 근데 var 키워드로 선언했기 때문에 초기화 과정을 거쳐 Record 안에는 우선적으로 favoriteFood : undefined가 기록되게 됩니다. 이러한 이유때문에 변수 선언문 이전에 변수를 콘솔로 찍어보면 undefined가 나오게 되는겁니다.

     

    그럼 이러한 호이스팅 문제를 해결하기 위해 나온 const, let은 어떻게 동작하게 될까요? 사실 호이스팅이 일어나지 않는다는 말은 아니라고 생각합니다. const와 let 역시도 호이스팅은 일어나게 됩니다. 자바스크립트 엔진은 const와 let 키워드로 선언된 변수 역시 Record에 미리 기록해두게 됩니다. 하지만 var와 달리 const와 let 키워드는 초기화 단계를 거치지 않게됩니다. 따라서 favoriteFood에 아무런 값도 없게됩니다. 그래서 변수 선언문 이전에 호출을 하게된다면 참조할 값이 없기 때문에 Reference Error가 발생하게 되는것이죠.

     

    이렇게 const, let 이전에 식별자를 참조할 수 없는 구역을 일시적 사각지대라고도 합니다. 여기까지 오셨다면 함수 호이스팅은 사실상 쉽게 이해할 수 있습니다.

     

    함수 호이스팅은 함수 선언문으로 함수를 선언했을 때 자바스크립트 엔진이 함수를 보고 완성된 함수 객체를 생성해 Record 내부에 기록해두게됩니다. 그래서 함수 선언문 이전에 함수를 실행 시켰을 때 실행이 가능하게 됩니다.

     

    하지만 함수 표현식을 사용해 함수를 선언하면 이런 함수 호이스팅이 발생하지 않게되는데요. 사실상 쉽게 알 수 있습니다. var 키워드를 사용해 함수 표현식으로 함수를 선언했다고 생각했을 때, Record 내부에는 식별자와 undefined가 할당되어 있겠죠? var 키워드는 초기화를 시켜주니까요. 그래서 var 키워드로 함수 표현식을 작성한 선언문 이전에 함수를 실행시켜보면 undefined를 실행하기 때문에 Type Error가 뜨게됩니다. const와 let 키워드를 사용하게 되면 초기화 단계를 거치지 않기 때문에 아예 값이 없는 상태가되면서 Reference Error가 뜨게 되는겁니다.

     

    이렇게 실행컨텍스트의 Record를 통해 호이스팅에 대해 설명해보았습니다. 하지만 여기서 끝이 아닙니다. 위에서 말했던 Record 말고 다른 뭔가가 더 있다고 하지 않았나요? 바로 Outer Record Reference, 외부 환경 참조입니다. 간략히 Outer라고 지칭하겠습니다. 이 Outer를 활용해 스코프체이닝, 클로저에 대해서도 한 번 알아보죠.

     

    3. Outer를 타고타고 스코프체이닝

    Outer는 외부 환경 참조라는 명칭과 같이 외부 Lexical Environment를 가리킵니다. 위에서 언급했듯이 여기서 렉시컬 환경이란 Record와 Outer를 합친 것을 의미합니다. 외부 렉시컬 환경을 가리킨다? 사실 한번에 이해하기에는 어려울 수 있습니다. 예제를 한가지 들어보겠습니다.

     

    let 키워드를 사용해 light 라는 변수를 선언하고 false를 할당해줍니다. 불이 꺼져있다고 생각하시면 편할 것 같습니다. 이후 함수 하나를 선언해줍니다. 이름은 2층이라고 얘기를 해볼게요. 이 2층이라는 함수 안에는 light를 true로 바꿔줍니다. 불이 켜지게 된다고 생각하시면 편합니다. 이제 컨텍스트 관점으로 한 번 살펴볼까요?

     

    자바스크립트 코드를 만났으니 엔진인 우리는(언제 엔진이 됐는지) 이제 전역 실행컨텍스트를 생성합니다. 이후 Record 내부에 light와 함수 2층이 담기게됩니다. Outer는 외부 렉시컬 환경을 가리키게 되는데 전역 실행컨텍스트는 최상위에 위치하기 때문에 Outer에는 아무것도 가리키지 않게됩니다. 이제 함수 2층을 실행시켜보겠습니다.

     

    함수 2층이라고 한 이유는 콜 스택 내부에 쌓이는 것을 그냥 층으로 표시하기 위해서 그런것입니다. 이제 2층 함수를 실행하면 콜 스택에 2층 함수 컨텍스트가 생성되게 됩니다. 2층 컨텍스트 Record 안에는 이제 light가 true 값으로 기록됩니다.

     

    이 때 자바스크립트 엔진은 고민에 빠지게됩니다. 아니 light가 false 값도 있고, true 값도 있는데 뭘로 값을 정해야하지? 생각하게 됩니다. 이렇게 동일한 식별자가 여럿일 때 변수나 함수의 값을 결정하는 것을 식별자 결정이라고합니다.

     

    이제 자바스크립트 엔진이 Outer를 활용해 식별자 결정을 하는 과정을 한 번 알아보겠습니다.

     

    다시 코드로 돌아와 함수를 실행하고, 컨텍스트 내부 Record 안에 light가 기록되고, Outer는 상위 컨텍스트인 전역 컨텍스트의 렉시컬 환경을 가리킵니다. 마치 전역 컨텍스트로 돌아갈 수 있는 사다리를 놓았다고 생각하면 이해하기 쉬울거라 생각됩니다. 자 이렇게 Outer가 설정되는 것을 알아보았으니 2층 함수 내부에 3층이라는 함수를 선언하겠습니다. 3층 함수 내부에는 pet 이라는 변수를 선언하고, cat을 할당해줍니다. 또한 내부에서 콘솔로 light와 pet, food를 찍어보겠습니다.

     

    이제 3층 함수를 실행하게 되면 3층 함수 실행컨텍스트가 콜스택에 쌓이게 됩니다. 그리고 Record에 pet이 cat이라는 값을 갖고 기록이되게 됩니다. Outer는 이제 2층 함수 실행 컨텍스트의 렉시컬 환경을 가리킵니다.

     

    자 이제 3층 함수에서 pet 식별자를 찾는다면 3층 함수 컨텍스트 내부 Record에 있기 때문에 cat이라는 값을 불러오게 됩니다. 다음은 food 식별자를 찾는 과정인데요. 3층 함수 컨텍스트 내부에 존재하지 않기 때문에 Outer를 통해 2층 함수의 렉시컬 환경에 있는 Record를 살펴봅니다. 어라? 근데 이 곳에도 없습니다. 그럼 2층 함수의 Outer를 통해 전역 컨텍스트의 Record를 보고 전역 컨텍스트에도 없다면 아 food는 없구나!! 하고, Reference Error가 발생하게됩니다.

     

    이 다음은 light를 찾는 과정입니다. 식별자 결정을 하는 과정인데요. 3층에서 light를 찾을 수 없기 때문에 Outer를 통해 2층 함수 렉시컬 환경 Record를 살펴보게되고 light 식별자를 참조해 true 값을 보여주게됩니다. 이렇게 보여주고 나면 전역 컨텍스트 내부에 있는 light 값은 false인지 true인지 모르게되는데요. 이렇게 동일한 식별자로 인해 상위 스코프에서 선언된 식별자 값이 가려지는 현상을 변수 섀도잉이라고합니다.

     

    이렇게 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 스코프 체인이라고 Outer를 통해 외부 렉시컬 환경에 접근하며 식별자를 결정해 나가는 과정을 스코프 체이닝이라고 합니다.

     

    이렇게 Outer를 통해 스코프 체이닝을 알아보았는데요. 이 Outer를 활용해 클로저 역시도 설명할 수 있다는 거 알고계셨나요? 클로저는 다른 글에서 또 설명하겠습니다.

     

    어렵기만 했던 실행컨텍스트에 한 층 더 가까워지는 계기가 되었으면 좋겠습니다.

     

     

     

    - 출처 : 하루의 실행 컨텍스트

    반응형

    댓글