Compilation

호이스팅, 스코프, 클로저JavaScript의 실행과정에 대한 마지막 개념으로 우리는 컴파일레이션에 대한 이해가 필요하다.

IR(중간 언어)로의 컴파일 과정을 단순화 한다면, 소스코드를 파싱한 뒤 AST를 생성하여 중간 언어인 바이트 코드컴파일하는 과정으로 볼 수 있다.

이때 우리가 알고자 하는 var를 쓰지 않는 이유와 밀접한 관련이 있는 부분은 바로 파싱과정이다.

파싱과정을 나눈다면 크게 아래와 같이 나눌 수 있다.

1. 토크나이징/렉싱

2. 파싱

그럼 해당 단계들에 대해 간단히 짚고 넘어가보자.

Tokenize & Lexing

토크나이징/렉싱은 JavaScript Engine이 소스 코드를 만났을 시 가장 먼저 진행하는 부분이다.

토크나이징렉싱의 차이점에 대해서는 간단하게 설명할 수 있다.

var a = 5;

여기서 var, a, =, 5, ;로 나누는 것이 토크나이징이라 할 수 있다.
즉 우리가 작성한 소스코드를 텍스트화 하여 가장 기본 단위로 잘개 쪼개는 과정이라 할 수 있다.

만약 해당 과정 중 상태 유지 파싱 규칙을 적용해 a별개의 토큰인지, 다른 토큰의 일부인지 파악한다면 그것을 렉싱이라 한다.

이 단계에서 어휘(렉시컬)단위를 변수, 예약어 등와 같이 분류하기도 하고 토큰들의 연관성을 분석하기도 한다.

이 과정 중 스코프가 정의된다면 그것을 렉시컬 스코프라고 한다.

Parsing

파싱은 다시 구문 분석과 의미 해석 으로 나누어진다.

Syntax Analysis(구문 분석)

구문 분석은 코드에서 구조를 분석하는 과정이다. 텍스트가 예상 형식을 따르는지 여부를 확인하여 작성한 소스 코드의 오류 유무를 체크한다.

또한 해당 언어의 규칙을 기반으로 토큰을 가지고 `구문 트리를 생성한다

Semantic Analysis(의미 분석)

의미 분석은 의미론적 일관성을 확인한다.

의미 분석은 구문 트리를 해석해서 필요 없는 부분을 삭제하기도 하고, 필요한 부분을 추가하기도 하여 AST(추상 구문 트리)를 생성한다.
구문 분석이 단순 코드의 외형적인 해석이라면 의미 분석은
변수 선언과 참조를 연결한다던지, 스코프를 구별한다던지, 타입 불일치를 판별한다던지, 선언되지 않은 변수를 확인한다던지, 의미론적으로의 연결성, 일관성을 체크한다.


참조 : What is JavaScript AST, how to play with it? (stack overflow)

Compile

위에 완성된 AST를 가지고 네이티브 코드, 혹은 바이트 코드로 변환하는 과정을 컴파일이라고 한다.

Scope

프로그래밍은 변수나 함수에 이름을 부여하여 의미를 갖도록 한다. 만약 이름이 없다면, 변수나 함수는 다만 그저 하나의 메모리 주소에 지나지 않는다.

초기 프로그래밍 언어는 이 대응표를 프로그램 전체에서 하나로 관리했는데, 이는 이름 충돌 등 다양한 문제를 발생시켰다. 그래서 충돌을 피하기 위해, 각 언어마다 "스코프"라는 규칙을 만들어 정의하였다. 그렇게 스코프 규칙은 언어의 명세(Specification)가 되었다.

즉 스코프는 참조 대상 식별자(identifier)를 찾아내기 위한 규칙이다.

변수, 함수의 이름과 같이 우리가 선언한 식별자를 찾아내 참조 할 수 있는지 없는지를 판단하는 기준이며, 식별자는 스코프를 통해 자신만의 유효한 범위를 갖는다.

var x = 'global';

function foo () {
  var x = 'local';
  console.log(x);
}

foo(); // 'local'
console.log(x); // 'global'

스코프는 크게 동작, 레벨을 기준으로 분류할 수 있다.

Definition by Allocation

Lexical Scope

렉시컬 스코프(정적 스코프)는 식별자가 정의(선언)될 때 결정된다.

var x = 'static'; 

function foo(){ 
  var x = 'local'; 
  bar();              
}

function bar(){ 
  console.log(x); // "static"
}

foo(); // "static"

위 코드를 보면

  1. foo()xlocal로 바꾸고 bar()를 호출한다.
  2. bar()x를 출력하지만, 정적 스코프 언어에서 x는 이미 전역 변수로 선언되었기 때문에 그대로 static을 출력한다.

Dynamic Scope

동적 스코프는 식별자가 실행, 혹은 호출될 때 결정된다.

var x = 'dynamic'; 

function foo(){ 
  var x = 'local'; 
  bar();              
}

function bar(){ 
  console.log(x); // "local"
}

foo(); // "local"

위 코드를 보면

  1. foo()정적 스코프일때와 마찬가지로 xlocal로 바꾸고 bar()를 호출한다.
  2. bar()x를 출력하지만, 동적 스코프 언어에서는 bar()foo()에 의해 호출되었으므로 foo()가 가지고 있는 변수 x의 값인 local을 출력한다. 즉 x전역 변수로 선언 하던 말던 본인이 실행되거나 호출 되었을때의 스코프유효범위로 가진다.

참고로 JavaScript는 렉시컬 스코프를 가진 언어이며, 함수 레벨 스코프를 가졌지만, ES6부터는 블록 레벨 스코프도 가진다.

Definition by Level

위에 동작 스코프가, 정적인지, 동적인지에 따라 분류된다면, 레벨 스코프유효 범위에 의한 분류이다.

Global Scope

전역 스코프는 말그대로 지역 전체의 스코프이며 어디서든지 참조가 가능하다.

만약 하나의 html에서 다수의 js파일을 로드해서 사용하더라도 전역 스코프를 가진 변수는 사용이 가능하다.
이는 전역 변수는 전역 객체의 프로퍼티이기 때문인데 브라우저의 window처럼 전역 객체는 실행 컨텍스트에 컨트롤이 들어가기 전부터 기본으로 생성되기 때문이다.
var x = 'global'; 

function foo(){ 
  var y = 'local'; 
  console.log(x); // 'global'
  console.log(y); // 'local'
}
foo();

console.log(x); // 'global'
console.log(y); // Uncaught ReferenceError: y is not defined

위 코드를 보면

  1. x는 어떠한 함수나 객체 안이 아닌 전역에 선언한 전역 변수다.
  2. yfoo() 안에 선언된 지역 변수다.
  3. foo() 안에서는 xy 모두 정상적으로 출력되는 걸 볼 수 있는데, x가 출력된 이유는 x가 어디서든지 참조가 가능한 전역 스코프 변수이기 때문이다.
  4. 하지만 전역에서 y는 정상적으로 출력되지 않는데, 이는 yfoo()에서만 유효한 범위를 가지는 지역 스코프 변수이기 때문이다.

Local Scope

지역 스코프는 함수 레벨 스코프블록 레벨 스코프로 나누어 진다.

Function Level Scope

함수 레벨 스코프함수를 유효범위로 가진다.
(var로 선언한 변수, 함수들을 함수 레벨 스코프를 가진다. )

function foo(level){
  if(level){
    var x = level + ' scope';
  }
  console.log(x);
}

foo('function level') // "function level scope"

/*
var x 는 if(){}구문 안에 선언되었지만 function level scope이므로 
function foo(){}안에서도 참조가 가능하다.
*/

Block Level Scope

블록 레벨 스코프블록 { }을 유효범위로 가진다.
(let,const 로 선언한 변수, 함수들을 함수 레벨 스코프를 가진다. )

function foo(level){
  if(level){
    let x = level + ' scope';
  }
  console.log(x);
}

foo('block level') //  Uncaught ReferenceError: x is not defined

/*
let x 는 if(){}구문 안에 선언되었기 때문에 block level scope이므로 if{}을 벗어난
function foo(){}안에서 참조가 불가능하다.
*/
profile
const isInChallenge = true; const hasStrongWill = true; (() => { while (isInChallenge) { if(hasStrongWill) {return 'Success' } })();

0개의 댓글