본문 바로가기

Language/JavaScript

<JS> 스코프란

1. 컴파일러 이론

* 스코프 소개

 - 프로그래밍 언어라면 변수에 값을 저장하고 그 값을 가져다 쓰고 수정할 수 있다. 하지만 여기서 의문이 드는 점이 있다. 변수는 어디에 있는가? 변수가 필요할 때 프로그램은 그것을 어떻게 찾는가?

 

 - 위 질문에서 알 수 있듯 변수는 어디에 저장되는지, 어떻게 찾을지를 정의한 규칙이 필요하다. 이것이 바로 스코프이다.

 

* 컴파일러 이론

 - 자바스크립트는 일반적으로 인터프리터 언어로 분류한다. 하지만 실제로는 컴파일러 언어이다. 다른 컴파일러 언어처럼 코드를 미리 컴파일하거나 컴파일한 결과를 분산시스템에서 이용할 수 있는 것은 아니다. 그럼에도 불구하고 자바스크립트 엔진은 컴파일러가 하는 일들을 우아하게 처리하고 있다.

 

 - 컴파일러 언어의 처리 과정을 살펴보자. 소스 코드가 실행 되기 전에 컴파일레이션이라는 3단계 과정을 거치게 된다.

 

 - 토크나이징/렉싱 : 문자열을 토큰이라는 의미있는 조각으로 나누는 과정을 의미한다. "var a = 2;" 를 "var", "a", "=", "2", ";" 와 같이 나누는 것이다. 빈칸이 의미가 있다면 이 또한 토큰으로 남긴다. (토크나이징과 렉싱은 미묘한 차이가 있으나 여기서는 무시하겠다.)

 - 파싱 : 토큰 배열을 프로그램의 문법 구조를 반영하여 중첩 구조를 가진 트리 형태로 바꾼다. 이 결과 AST(추상 구문 트리)가 생성된다.

 - 코드 생성 : AST를 실행 코드로 바꾸는 과정이다. 

 

 - 자바스크립트 엔진은 위의 과장보다 좀 더 복잡하다. 불필요한 요소를 삭제하며 성능을 최적화하는 과정이 코드 생성 이전에 존재한다.

 

 - 자바스크립트 엔진이 기존 컴파일러와 가장 크게 다른 점은 컴파일레이션을 미리 수행하지 않는다는 점이다. 이는 코드가 실행되기 직전에 수행된다. 즉 자바스크립트는 실행을 위해 바로 직전에 컴파일하고 실행한다. 

 

2. 스코프

* 과정

 - 스코프를 이해하기 위해서는 "엔진", "컴파일러", "스코프"에 대해서 알아야 한다.

 

 - 엔진은 컴파일레이션의 시작부터 끝까지 전 과정과 자바스크립트 프로그램 실행을 담당한다.

 

 - 컴파일러는 파싱과 코드생성의 역할을 한다.

 

 - 스코프는 선언된 모든 확인자(변수) 검색 목록을 작성하고 유지한다. 또한 엄격한 규칙을 강제하여 현재 실행 코드에서 확인자의 적용 방식을 정한다.

 

 - 위에서 봤었던 "var a = 2;" 를 우리는 하나의 구문으로 보지만 실제로 엔진은 2개의 구문으로 본다. 하나는 컴파일레이션 과정에서 처리할 구문, 다른 하나는 실행 과정에서 엔진이 처리할 구문이다.

 

 - 컴파일러가 "var a" 를 만나면 스코프 컬렉션에 a가 있는지 묻는다. a가 있으면 무시하고, 있다면 새로운 변수 a를 스코프 컬렉션에 선언하라고 요청한다. 그 후 컴파일러는 "a = 2" 대입문을 처리하기 위해 나중에 엔진이 실행할 수 있는 코드를 생성한다.

 

 - 엔진이 실행하는 코드는 a 라는 변수가 스코프 컬렉션 내에서 접근할 수 있는지 확인하고 있으면 사용한다. 없다면 중첩 스코프를 찾아 나선다. 끝까지 못찾는다면 에러를 내뱉는다.

* 컴파일러체

 - 컴파일러 관련 용어를 좀 더 살펴보자.

 

 - LHS 검색 : 변수가 대입 연산자의 왼쪽에 있을 때 수행한다. 값을 넣을 때, 변수 컨테이너 자체를 찾는 것을 의미한다.

 

a = 2;

 

 - 위는 a 값을 신경 쓸 필요 없이 "= 2" 대입 연산을 수행할 대상 변수를 찾으므로 LHS이다.

 

 - RHS 검색 : 변수가 대입 연산자의 오른쪽에 있을 때 수행한다. 단순히 특정 변수의 값을 찾는 것이다.(정확하게는 왼쪽이 아닌 모든 방향)

 

console.log(a);

 

 - a에 아무것도 대입하지 않으며, a의 값을 가져와 console.log에 넘겨준다. 따라서 a에 대한 참조는 RHS 참조다.

 

3. 중첩 스코프

 - 하나의 블록이나 함수는 다른 블록이나 함수 안에 중첩될 수 있으므로 스코프도 다른 스코프안에 중첩될 수 있다. 따라서 위에서 언급했듯이 변수를 현재 스코프에서 발견하지 못하면 엔진은 다음 바깥의 스코프로 넘어가는 식으로 변수를 찾거나 글로벌 스코프에 도달할때까지 찾는다.

 

function foo(a) {
  console.log(a + b);
}
var b = 2;
foo(2); // 4

 

 - b에 대한 RHS 참조는 foo 안에서 처리할 수 없고, 글로벌 스코프에서 처리하게 된다.

 

 - 내부적으로 엔진은 LHS/RHS 를 참조하기 위해 현재 스코프를 확인하고, 찾지 못하면 외부 스코프를 차례로 확인한다. 글로벌 스코프에 도달하면 찾았든 찾지 못했든 중단한다.

 

4. 오류 

- LHS와 RHS를 왜 구분해야할까. 그 이유는 변수를 찾지 못했을 때 다르게 동작하기 때문이다.

 

function foo(a) {
  console.log(a + b);
  b = a;
}

foo(2);

 

 - b에 대한 RHS 검색이 실패하면 엔진은 'ReferenceError" 를 발생시킨다. 반면에 엔진이 LHS 검색을 수행해 변수를 찾지 못한다면 글로벌 스코프는 검색 중인 이름을 가진 새로운 변수를 생성한다. 즉 에러가 발생하지 않는다.

 

 - 다행히 Strict Mode 를 사용하면 글로벌 변수를 암시적으로 생성할 수 없기 때문에 동일하게 'ReferenceError'을 발생시킨다.

 


참고

 

 해당 포스팅은 You Don't Know JS 를 읽고 개인적으로 필요한 내용을 추가 및 정리한 글입니다.

 

 

링크

 

 

GitHub - getify/You-Dont-Know-JS: A book series on JavaScript. @YDKJS on twitter.

A book series on JavaScript. @YDKJS on twitter. Contribute to getify/You-Dont-Know-JS development by creating an account on GitHub.

github.com

 

 

You Don’t Know JS - YES24

모호하고 애매한 여덟 가지 자바스크립트 개념 길라잡이_You Don’t Know JS 시리즈웹 초창기 시절부터 자바스크립트는 사람들이 대화하듯 웹 콘텐츠를 소비할 수 있게 해준 기반 기술이었다. 20년

www.yes24.com

 

 

 

'Language > JavaScript' 카테고리의 다른 글

<ES2022> ES2022 Features  (0) 2022.04.03
<JS> 암시적 변환  (0) 2021.12.28
<JS> 특수 값과 레퍼런스  (0) 2021.12.23
<ES2021> ES2021 Features  (0) 2021.10.25
<ES2020> ES2020 Features  (0) 2021.10.24