스코프 이해하기
스코프를 좀더 재미있고 쉽게 설명하기 위해 대화 형식으로 스코프를 보자. 먼저 등장 인물을 살펴보자.
출연진
프로그램 "var a = 2;"
를 처리할 주역들을 만나보자. 그래야 나중에 들을 대화를 이해할 수 있다.
- 엔진 : 컴파일레이션의 시작부터 끝까지 전 과정과 자바스크립트 프로그램 실행을 책임진다.
- 컴파일러 : 엔진의 친구로, 파싱과 코드 생성의 모든 잡일을 도맡아 한다.
- 스코프 : 엔진의 또 다른 친구로, 선언된 모든 확인자(변수) 검색 목록을 작성하고 유지한다. 또한, 엄결한 규칙을 강제하여 현재 실행 코드에서 화인자의 적용 방식을 정한다.
만약 자바스크립트가 어떻게작동하는지 완전히 이해한다면 엔진처럼 생각해보자. 그들이 던지는 질문을 던지고 그들과 똑같이 답해보자.
앞과 뒤
프로그램 "var a = 2;"
를 보면 하나의 구문으로 보인다. 그러나 우리의 새로운 친구 엔진은 그렇게 보지 않는다. 사실 엔진은 두 개의 서로 다른 구문으로 본다. 하나는 컴파일러가 컴파일레이션 과정에서 처리할 구문이고, 다른 하나는 실행과정에서 엔진이 처리할 구문이다.
이 프로그램에서 컴파일러가 할 첫 번째 일은 렉싱을 통해 구문을 토큰으로 쪼개는 것이다. 그후 토큰을 파싱해 트리 구조로 만든다. 그러나 코드 새엇ㅇ과정에 들어가면 컴파일러는 몇몇 독자의 추측과는 다르게 프로그램을 처리한다.
컴파일러가 다음 의사 코드로 요약될 수 있는 코드를 생성한다고 생각할 수 있다.
변수를 위해 메모리를 할당하고 할당된 메모리를 a라 명명한 후 그 변수에 값 2를 넣는다.
안타깝지만, 이는 그리 정확한 설명이 아니다. 컴파일러는 다음 일을 진행한다.
- 컴파일러가
var a
를 만나면 스코프에게 변수 a가 특정한 스코프 컬렉션 안에 있는지 묻는다. 변수 a가 이미 있다면 컴파일러는 선언을 무시하고 지나가고, 그렇지 않으면 컴파일러는 새로운 변수 a를 스코프 컬렉션내에 선언하라고 요청한다. - 그 후 컴파일러는
a = 2
대입문을 처리하기 위해 나중에 엔진이 실행할 수 있는 코드를 생성한다. 엔진이 실행하는 코드는 먼저 스코프에게 a라 부르는 변수가 현재 스코프 컬렉션 내에서 접근할 수 있는지 확인한다. 가능하다면 엔진은 변수 a를 재사용하고, 아니라면 엔진은 다른곳을 살핀다.
엔진이 마침내 변수를 찾으면 변수에 값 2를 넣고, 못찾는다면 엔진은 손을 들고 에러가 발했다고 소리칠 것이다! 요약하면, 별개의 두 가지 동작을 취하여 변수 대입문을 처리한다. 첫째, 컴파일러가 변수를 선언한다. 둘째, 엔진이 스코프에서 변수를 찾고 변수가 있따면 값을 대입한다.
컴파일러체
더 나아가기 전에 컴파일러 관련 용어를 약간 더 살펴보자.
2단계에서 컴파일러가 생성한 코드를 실행할 때 엔진은 변수 a가 선언된 적이 있는지 소스코드에서 검색한다. 이때 엔진이 어떤 종류의 검색을 하느냐에 따라 검색 결과가 달라진다. 앞의 경우에서 엔진은 변수 a를 찾기 위해 LHS 검색을 수행한다. 다른 종류의 검색은 RHS라 부른다. 여기서 'L'과 'R'이 무엇을 뜻하는지 예상할 수 있을 것이다. L과 R은 각각 '왼쪽 방향'과 '오른쪽 방향'을 뜻한다.
다른 말로 하면 LHS 검색은 변수가 대입 연산자의 왼쪽에 있을 떄 수행하고 RHS 검색은 변수가 대입 연산자의 오른쪽에 있을 때 수행한다.
RHS 검색은 단순히 특정 변수의 값을 찾는 것과 다를 바 없다. 반면, LHS 검색은 값을 넣어야 하므로 변수 컨테이너 자체를 찾는다. 따라서 정확히 말하면 RHS는 그자체로는 '대입문의 오른쪽'이 아니다. 좀 더 정확히 말하면 '왼편이 아닌 쪽'에 가깝다.
좀더 수비게 말하면 RHS를 "Retrieve(가져오라) his/her(그의/그녀의) sourve(소스)"의 약자라고 보면 RHS는 "가서 값을 가져오라"라는 뜻으로 이해할 수 있따. 이제 좀 더 깊게 파보도록 하자.
console.log(a);
a에 대한 참조는 RHS 참조다. 구문에서 a에 아무것도 대입하지 않기 때문이다. 대신 a의 값을 져와 console.log()
에 넘겨준다. 다른 예제를 보자.
a = 2;
a에 대한 참조는 LHS참조다. 현재 a값을 신경 쓸 필요 없이 = 2
대임 연산을 수행할 대상 변수를 찾기 때문이다.
LHS, RHS가 '대입문의 왼쪽/오른쪽'을 뜻한다고 해서 반드시 문자 그대로 '대입 연산자의 왼쪽과 오른쪽'을 지칭하는 것은 아니다. 대입 연산은 다른 여러 방식으로 일어날 수 있다. 따라서 개념적으로는 대입할 대상과 대입한 값이라고 생각하는 것이 더 낫다.
LHS와 RHS 참조들 모두 수행하는 다음 프로그램을 보자.
function foo(a) {
console.log(a); //2
}
foo(2);
마지막 줄에서 foo()
함수를 호출하는 데 RHS 참조를 사용한다. 즉, "가서 foo의 값을 찾아 내게 가져와라"라는 뜻이다. 여기서 ()
는 실행한다는 뜻이므로 foo는 함수여야 한다.
이 부분에 미묘하지만 중요한 ㅏ대입이 수행된다. 무엇을 가리키는지 알겠는가?
앞의 코드 속에 내재된 a = 2
를 놓쳤을지도 모른다. 인수로 값 2를 함수 foo()
에 넘겨 줄 때 값 2를 인자 a에 대입하는 연산이 일어난다. 이 인자 a에 대한 대입 연산을 위해 LHS 검색이 수행된다.
변수 a에 RHS 참조 역시 수행되는데,, 그 결괏값은 console.log()
함수에 넘겨진다. 또 console.log()
가 실행되려면 참조가 필요하다. console
객체를 RHS 검색하여 log
메서드가 있는지 혹인한다.
마지막으로 값 2를 RHS로 불러온 변수 a를 통해 log()
에 넘겨주는 과정에서 RHS/LHS를 주고 받는 작업에 대한 개념을 짚어보자. 구현된 log()
의 내부에는 인자가 있을 것이고, 첫 번째 인자를 LHS 검색으로 찾아 2를 대입할 것이다.
엔진과 스코프의 대화
function foo(a) {
console.log(a); // 2
}
foo(2);
이 코드의 실행 과정을 대화라고 상상해보자. 그 대화는 이럴 것이다.
- 엔진 : 안녕 스코프? foo에 대한 RHS 참조가 필요해, foo라고 들어본적 있어?
- 스코프 : 응 들어봤어. 컴파일러가 좀 전에 선언하더라고, foo는 함수야. 이걸 보면 돼.
- 앤진 : 좋아 고마워! 자, 이제 나는 foo를 실행해야 겟어
- 엔진 : 이봐 스코프. a에 대한 LHS 참조도 구해야 하는데, 들어본 적 있어?
- 스코프 : 물론이지 컴파일러가 a를 foo의 인자로 좀 전에 선언했어. 이걸 봐
- 엔진 : 항상 도와줘서 고마워, 스코프. 정말 고마워, 이제 2를 a에 대입할 식나이야
- 엔진 : 스코프, 자꾸 귀찮게 해서 미안해 console에 대한 RHS 검색이 필요해. 해줄수 있을까?
- 스코프 : 문제없어, 엔진. 이게 내가 항상 하는 일이잖니. 자, console을 찾았어. 내장돼 있더라. 이거야
- 엔진 : 완벽해. 이제 log()를 찾아볼까. 좋아 멋져, 이건 함수구나
- 엔진 : 요~스코프! a의 RHS 참조를 찾는 것좀 도와줄 수 있을까? 나한테도 있긴 할테지만, 확실히 해두고 싶어
- 스코프 : 옳은 말이야, 엔진. 변함없이 엄밀하는구나. 여기 있어.
- 엔진 : 멋져. 이제 a의 값을 .. 값은 2구나. log()에 넘기자.
참고
- You Don't Know JS - 타입과 문법, 스코프와 클로저( 한빛 미디어 )
'JAVASCRIPT' 카테고리의 다른 글
[JS][렉시컬 스코프] 검색 (0) | 2022.02.15 |
---|---|
[JS][스코프] 중첩 스코프 와 오류, 정리하기 (0) | 2022.02.15 |
[JS][스코프] 스코프란? (0) | 2022.02.15 |
[JS][문법] Switch (0) | 2022.02.14 |
[JS][문법] 연산자 우선순위 (0) | 2022.02.14 |
댓글