Lexical Scope and Dynamic Scope

Tags: JavaScriptlexical scopelexicalscope

July 12, 2015

C/C++, Java, 그리고 JavaScript 같이 우리가 접하는 대부분의 언어들은 Lexical Scope를 사용한다. Lexical Scope는 Static Scope라고도 불린다. 반대의 방식으로는 Dynamic Scope가 있으며 Perl, Bash Shell, APL 같은 몇몇 오래된 언어들이 사용하는 방식이다. 두 방식의 정의는 다음과 같다.

Lexical scope: use environment where function [and variable] is defined

Dynamic scope: use environment where function [and variable] is called

출처: University of Washington CSE341 2014 Spring - Lecture 9

Lexical Scope는 변수나 함수가 정의 된 곳의 context를 사용하며, Dynamic Scope는 변수나 함수가 불려진 곳의 context를 사용한다. Lexical Scope는 아주 익숙한 개념이므로 Dynamic Scope에 대해서만 간단히 설명하고, Lexical Scope가 JavaScript에서 어떻게 쓰이는 지 적어볼까 한다.

Dynamic Scope

// JavaScript with Dynamic Scope
function foo() {
  console.log(x);
}

function bar() {
  var x = 15;
  foo();
}

var x = 10;
foo(); // 10
bar(); // 15

위의 코드는 JavaScript에 Dynamic Scope가 적용되었다고 가정했다. — 실제로 실행할 경우 다른 결과값이 나온다. 모든 결과값은 Line 3의 x의 값을 읽은 타이밍에 따라서 — 이름 그대로 동적으로 — 달라진다. Line 12에서 처음으로 foo function이 호출될 때 x의 값은 10이므로 당연히 결과값은 10이 출력된다. Line 13에서도 x의 값은 10이지만 bar function내에서 x를 15으로 재선언 뒤 foo function을 호출하기 때문에 결과값이 15로 바뀌게 된다.

언어에 따른 Lexical Scope의 차이

Block Scope

C계열의 언어들은 모든 block이 자신의 scope를 가진다.

// C
void main() {
  int x = 1;
  printf("%d", x); // 1
  if(1) {
    int x = 2;
    printf("%d", x); // 2
  }
  printf("%d", x); // 1
}

if block 자체가 자신의 scope를 가지고 있으므로 main function의 scope에는 영향없이 따로 x의 값을 가질 수 있다. if block이 지난 뒤에는 다시 main function의 scope에 접근하게 된다.

Function Scope

JavaScript는 Function Scope를 사용한다. function만이 자신의 scope를 가진다.

// JavaScript
function foo() { // foo 
  var x = 1;
  console.log(x); // 1
  if(true) {
    var x = 2;
    console.log(x); // 2
  }
  console.log(x); // 2
}
 
foo();

Block Scope와 JavaScript에서는 function만이 scope를 가지기 때문에 if block 안에서 x값을 수정하면 foo function의 scope의 x에 값이 바뀌게 된다 — 실제 JavaScript의 if block 안에서 var를 다시 선언하는 건 좋은 코딩스타일은 아니다. 그러므로, if block이 끝난 뒤에도 수정된 값을 가지게 된다.

그럼 JavaScript에서 새 Scope 생성은?

당연한 이야기지만 Function Scope를 생성해야 하므로 필요한 곳에 function을 추가하면 된다.

// JavaScript
function foo() {
  var x = 1;
  if (true) {
    (function () {
      var x = 2;
      console.log(x); // 2
    })();
  }
  console.log(x); // 1
}
 
foo();

예제와 같은 경우는 if block의 한정된 곳에서 한번만 실행 될 코드이므로 Immediately-Invoked Function Expression(IIFE)를 추가한다. IIFE는 만들어지자마자 바로 실행되며 동시에 새로운 scope를 가진다. 독립적인 scope를 가지므로 그 안에서 선언된 x는 foo function의 scope에 영향을 미치지 않는다.

JavaScript에서 function을 이용한 Lexical Scope는 Closure를 이해하는데 아주 중요한 요소 중 하나이며 그 외에도 모듈화를 하는데도 빈번히 사용된다.

Let keyword in ES6

이번에 제정된 ES6(ECMAScript 2015)에서는 let keyword가 새로 추가되었다. var과 비슷하게 변수를 선언하는 keyword이지만 많은 부분에서 차이를 보인다.

  • var keyword

    • Function Scope
    • Hoisting
    • 중복선언 가능
  • let keyword

    • Block Scope
    • NO Hoisting
    • 중복선언 불가 (에러 발생)

참고글: