[JavaScript] 렉시컬 스코프 왜 이렇게 어렵게 설명해?

출력결과를 예상해보자.
var name = "junha"
function log() {
console.log(name)
}
function doMistake() {
var name ="stupid"
log();
}
doMistake() // ???
log함수가 실행되기 직전에, var name="stupid" 때문에 멈칫거릴 수 있다.
하지만, 결과는 junha가 나온다.
왜?
렉시컬 스코프는 함수가 선언될 때 스코프를 이미 다 정해둔다.
스코프는 식별자의 유효 범위라는 뜻으로, 변수를 어디까지 볼 수 있냐를 의미한다.
이 둘을 연결해보자.
" 함수는 선언될 때, 참조 가능한 변수 목록을 만들어, 고정시킨다. "
따라서 위의 예시에서 log라는 함수가 선언될 때, 참조 가능한 변수 목록에는 전역 변수 name은 있지만, doMistake함수 안에 name는 없다. 분리된 함수는 각각의 스코프를 갖기 때문이다. 따라서 log라는 함수는 doMistake라는 함수가 실행된 후에 호출되어, 실행되어도 여전히 junha값을 갖고 있는 name을 출력하게 된다. (실수해도 stupid가 아니라는 교훈을 얻을 수 있다.)
이 기세를 이어, 함수 스코핑
코드가 선언될 때, 스코프를 정해두는 것을 렉시컬 스코프라고 부른다는 것을 알 수 있었다.
이와 대비되는 개념은 함수 스코프, A.K.A동적 스코프라고 부른다.
동적 스코프는 함수의 실행 순서에 따라 스코프가 바뀌어 버린다.? (렉시컬 스코프 세계에서는 큰 충격!)
따라서 함수가 실행될 때, 가장 최근에 실행됐던 스코프를 따르게 된다.
생각보다 간단한데 왜 햇갈렸지?
실행 컨텍스트
콜스택
스코프 체이닝
이 개념들을 확실히 이해해야, 렉시컬 스코프를 완전히 이해할 수 있다.
자. 출력결과를 예상해보자.
var name = "junha"
function doMistake() {
var name ="stupid"
function log() {
console.log(name)
}
log()
}
doMistake() // ???
이는 위와 다르게 name이 바뀌어 stupid가 출력된다.
사실, 렉시컬 스코프를 제대로 이해했어도 충분히 햇갈릴만한다.
" log가 선언됐을 때, doMistake에 있는 name과 전역 변수인 name 모두 볼 수 있네..? "
" 이 둘 중에 어떤 name을 골라야 되지??? "
기억하자.
함수가 선언될 때, 실행 컨텍스트가 만들어진다.
실행 컨텍스트가 만들어지면 스코프는 스코프 체이닝을 통해 지정한다.
스코프 체이닝은 해당 실행 컨텍스트의 안에서부터 시작해서 밖으로 변수를 찾는다.
해당하는 변수를 찾으면, 더이상 찾지 않는다.
따라서 log함수는 doMistake에 선언된 변수 name을 스코핑한 순간 멈추며, 전역범위에 있는 name은 일면식도 없게 된다. 이때! 가장 중요한 개념을 놓치지 말아야 한다. 스코프체이닝은 콜스택 위치와 전혀 무관하다. 가장 처음에 봤던 예시의 콜스택을 보면, log함수가 doMistake함수위에 올려져있다. 이를 보면 log함수가 doMistake함수의 스코프를 체이닝한다고 생각할 수 있다. 스코프 체이닝은 **"실행 컨텍스트"**의 안에서부터 시작해서 밖으로 변수를 찾는다. 실행컨텍스트는 함수가 선언될 때 생성되므로, 호출 순서와 관련있는 콜스택과 햇갈리지 말아야한다.
정리
-
자바스크립트는 렉시컬 스코프를 따른다.
-
렉시컬 스코프는 함수가 선언될 때 스코프가 지정되는 것을 말한다.
여기까지만 알아도 충분한다.
좀 더 나아가, 함수가 선언될 때, 실행 컨텍스트가 생성되며, 실행 컨텍스트가 생성될 때, 그 실행 컨텍스트의 환경 변수들을 참조하여 스코프를 지정한다는 사실까지 알면 좋다. 이와 관련해 내부적으로 완벽히 이해하고 싶다면, 실행 컨텍스트의 세 가지 구조 및 LexicalEnvironmentRecord, OuterEnviornmetReference, EnviornmentRecord등을 찾아보길 바란다.