자바스크립트 스코프와 클로저
사라진 값을 기억한다구??
들어가며😃
클로저의 개념을 정리하기 전에 더 필수적으로 정리해야할 개념들이 있어 이전 포스팅에 정리해보았다. 공부하면 할수록 어림짐작만 되었던 자바스크립트 구조가 더 선명하게 보이고 로직들을 잘 이해하게 된다. 이번 포스팅에선 또 어떤 가르침을 줄지 기대가 된다. 어쨋던 돌고 돌아 클로저로 왔는데 확실히 실행 컨텍스트 관련 개념을 이해하니 클로저 파트도 이해하기 쉬워졌다. 이로써 나온 나의 포스팅도 누군가에게 쉽고 깔끔하게 다가오기를 😇.
스코프
해당 개념은 이전 포스팅에서 정리한 렉시컬 환경의 외부 환경 참조 부분과 식별자 개념에 아주 연관이 깊다. 함수가 실행되면 실행 컨텍스트가 쌓이고 식별자가 생성된다. 그리고 코드가 실행되어 해당 식별자에 바인딩 된 값을 찾아가면서 외부 컨텍스트의 환경 레코드를 참조했는데 이 과정이 모두 스코프와 관련된 것 이다.
-
스코프(Scope) : 변수에 접근할 수 있는
범위
로써 변수, 함수, 클래스의 식별자가 자신이 선언된 위치에 따라, 다른 코드에서 자신이 참조될 수 있을지 없을지에 대해 본인의 유효 범위가 정해진다.전역 스코프(Global Scope)
: 전역에 선언되어있어 어느 곳에서든지 해당 변수에 접근 가능.지역 스코프(Local Scope)
: 해당 지역에서만 접근할 수 있어 지역을 벗어난 곳에서는 접근 불가
-
스코프 체인(Scope Chain) : 식별자를 결정할 때 활용하는 스코프들의 연결리스트. 꼭대기 층의 실행 컨텍스트에서 식별자 값을 찾을 수 없었을 때에는 아래의 실행 컨텍스트에서 값을 찾고, 또 없으면 그 아래의 실행 컨텍스트에서 값을 찾는다. 이처럼 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 스코프 체인이라고 한다.
-
스코프 체이닝(Scope Chaining) : 식별자를 결정하기 위해 타고 타고 찾아가는 과정이다. 해당 과정을 이미지로 표현해보았다.
위의 그림처럼 현재 어떤 함수가 변수를 참조하려하는데 본인의 스코프에 값이 없다? 상위 스코프로 접근 -> 상위에도 없다? -> 더 상위 스코프 접근 -> 반복 -> 전역 스코프까지 돌았는데 변수가 없다? -> Reference Error 가 나는 것이다. 이 과정이 스코프 체이닝이다.
스코프 체인에서 변수를 참조할 때는 무조건 위로 올라간다. 하위 스코프에서 상위 스코프에 있는 변수를 참조할 수 있는 이유가 이런 스코프 체인의 반 방향성 때문이다.
스코프는 상위 스코프가 결정되는 시점을 기준으로 정적 스코프와 동적 스코프로 나뉜다.
-
동적 스코프
: 함수가 호출되는 시점에 결정 -
정적 스코프(렉시컬 스코프)
: 함수가 정의되는 시점에 결정자바스트립트는 렉시컬 스코프를 따르기 때문에 함수가 태어나자 마자 상위 스코프가 결정이 되고, 이후에 해당 함수에 의해 함수 객체가 생성되면 해당 함수 객체는 본인의 상위 스코프를 항상 알 수 있게 된다. 자바스크립트에서는 함수가 태어나면서 자신의 내부 슬롯에 상위 스코프의 참조를 저장하기 때문이다.
자, 위의 코드를 보면, outer 함수가 선언되어 있고, 그 안에 변수 x가 선언되어 있다. 그리고 내부에 중첩함수로 inner 함수가 표현되어있다. 이 outer 함수는 중첩함수 inner 함수를 newOuter 변수에 반환하면서 생명주기를 마감한다. 즉, outer의 함수 호출이 종료되면 outer 실행 컨텍스트는 콜스택에서 제거된다. 그렇다면 outer 함수에 선언되었던 지역 변수 x 또한 없어질테고 더이상 지역 변수 x 에 접근할 방법이 없다. 그런데 말이다,, 코드의 실행 값은 10이 나온다?
죽은 x가 다시 돌아왔다고? 🤷🏻♀️
클로저
위의 예제를 빗대어 설명하자면 중첩함수 inner가 이미 생명주기를 마감한 outer 함수의 지역 변수 x를 참조할 수 있다면, 이때 inner를 클로저라고 한다. 위의 코드의 순서를 간단하게 다시 정리해보자.
- outer 함수의 생명주기가 끝이 났다. 실행 컨텍스트가 제거된다.
- outer 함수는 newOuter 함수에게 값을 반환(return)하며 사라졌기 때문에 newOuter는 inner 함수 객체를 참조한다.
outer 함수는 실행 컨텍스트에서 제거가 되었지만 outer 함수의 렉시컬 환경까지 사라지지 않는다. newOuter 함수는 inner 함수 객체를 참조하고 있고, inner 함수 객체는 본인의 내부 슬롯에 저장된 outer 함수의 렉시컬 환경을 참조하기 때문에 가비지 컬렉션의 대상이 되지 않는것이다!! 위의 렉시컬 스코프에서 개념이 이어진다.
따라서 newOuter 에 의해 inner 함수를 다시 호출하면 outer 함수 내부에 있는 변수 x를 다시 참조할 수 있는 것 이다. 비록 inner 함수 객체 기준으로 outer 함수는 실행 컨텍스트에서 전부 사라졌지만 본인이 기억하고 있는 내부 슬롯에 저장된 상위 스코프에 의존하며, 상위 스코프 내의 식별자를 참조할 수 있는 것이다.
한번 더 정리를 해보자면 상위 스코프의 식별자를 참조하고 있고, 본인의 외부 함수보다 더 오래 살아있다? 그게 바로 클로저다.
클로저라는 개념을 알기 위해 참 많이 돌아왔다. 하지만 기초적이지만 꼭 알아야할 것들이었다. 이번 기회에 정리할 수 있어서 다행이라 생각한다. 처음에는 실행 컨텍스트..? 렉시컬 환경..? 그게 뭔데 뿌엥 ㅠ 이였지만 이제는 잘 이해할 수 있다. 오늘도 자바스크립트와 한층 친해졌다…그러니 내게와,,프론트,,🥴