본문 바로가기
Development

[21.01.02] YDKJSY - What's the Scope?

by igy95 2024. 1. 7.

Compiled vs Interpreted

컴파일 방식과 인터프리팅 방식은 기계가 이해할 수 있는 코드로 치환한다는 점에서 대체적으로 유사하다. 하지만 모든 코드가 한번에 변형되는 컴파일 방식과는 달리, 인터프리팅 언어는 한 줄 씩 코드를 읽고 적절한 변형을 거친다. 하지만 그렇다고 JS 엔진의 작동 방식이 딱 이거다, 라고 할 수 없는 이유가 현대의 JS 엔진은 컴파일과 인터프리팅의 방식을 각 단계마다 적절히 변형하고 사용하기 때문이다. 그래서 저자는 JS를 컴파일 방식에 조금 더 가까운 언어라고 묘사하고 있다.

Compiling Code

챕터의 서두에서 전에 언급한 주제를 다시 꺼낸 것은 이번에 다룰 스코프가 이 컴파일 단계와 밀접하게 관련된다는 점에 있다. 컴파일러 이론에 따르면, 컴파일로 진행되는 프로그램은 세 가지 기본 룰을 거친다고 한다.

 

  1. 토크나이징/렉싱: 코드 안의 문장들을 각각의 의미있는 조각으로 나누는 과정을 말하고, 나누어진 조각들은 '토큰' 이라고 불린다. 두 과정 사이의 차이점은 토큰을 인식하는 방법에 있다.
    • 토크나이징 => 무상태 방식
    • 렉싱 => 상태 유지 방식
  2. 파싱: 토큰들을 가져와 문법적인 구조로 이루어진 중첩 원소 트리로 바꿔 준다. 이때 이러한 트리를 '추상 구문 트리' (Abstract Syntax Tree) 라고 부른다.
  3. 코드 생성: 전 단계의 AST 를 실행가능한 코드(머신 코드)로 변형한다. 이 부분은 언어나 다른 요소들에 따라 다양한 방법이 있다.

Required: Two Phases

JS 엔진의 작동 방식을 최대한 단순히 다루어 보자면, 파싱 / 컴파일 후에 실행한다는 점이다. 내부에서 어떻게 동작하는지는 알기 힘들겠지만 위의 단계를 입증할 만한 몇 가지 예시들이 있다. 예상되지 않은 토큰이 입력되거나 JS 엄격 모드의 룰을 무시하는 등, Syntax error 가 발생하는 경우에는 그 전에 완전한 코드가 작성되었다 하더라도 그 값에 대한 적절한 실행이 일어나지는 않는다. 이것은 JS가 코드를 한 줄씩 처리하여 실행하지 않고, 전체 코드를 단계적으로 변환한다는 의미이다.

Compiler Speak

모든 변수, 식별자는 두 역할 중 하나를 수행한다 : 할당의 타겟이 되거나, 할당하려는 값이 된다.

Lexical Scope

스코프는 JS 엔진의 컴파일 단계에서 결정되고, 이러한 유형의 스코프를 lexical scope 이라고 한다. lexical은 컴파일의 렉싱 과정과 관련되어있다. 렉시컬 스코프는 범위에 따라 변수의 선언과 참조를 제어하는 역할을 하는데 참조의 경우, 사용된 변수의 해당 스코프에서 참조할 변수가 없을 때 그 다음 외부의 범위를 참조하게 된다. 이러한 과정은 일치하는 변수를 찾거나 전역 범위에 도달할 때까지 지속되는데 끝까지 찾지 못한다면 undeclared 오류가 발생하게 된다. 컴파일이 실제로 런타임 동안 스코프를 생성하는 것은 아니다. 다만 컴파일 하는 동안 스코프를 식별 후 맵 형식으로 모든 어휘 범위를 만들고 이것을 런타임에서 필요시 가져다 쓸 수 있게 한다.