5.10 Javascript Scope

변수에의 접근성과 생존기간(life-cycle)

Scope(유효범위)는 자바스크립트를 포함한 모든 프로그래밍 언어의 기본적인 개념으로 확실한 이해가 필요하다.

Scope란 변수에의 접근성과 생존기간(life-cycle)을 의미한다. 달리 말하자면 변수가 가지고 있는 참조 범위를 의미한다.

자바스크립트의 Scope 종류는 다음과 같다.

전역 Scope (Global scope)
코드 어디에서든지 참조할 수 있다.
지역 Scope (Local scope or Function-level scope)
정의된 함수 내에서만 참조할 수 있다.

모든 변수는 Scope를 갖는다.

전역 변수 (Global variable)
전역 Scope를 갖는 변수.
지역 변수 (Local variable)
지역 Scope를 갖는 변수

변수는 선언 위치(전역 또는 지역)에 의해 Scope를 가지게 된다. 즉 전역에서 선언된 변수는 전역 Scope를 갖는 전역 변수이고, 지역(자바스크립트의 경우 함수 내부)에서 선언된 변수는 지역 Scope를 갖는 지역 변수가 된다.

전역 Scope를 갖는 전역 변수는 전역(코드 어디서든지)에서 참조할 수 있다. 지역(함수 내부)에서 선언된 지역 변수는 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

자바스크립트의 Scope는 타 언어와는 다른 특징을 가지고 있다.

대부분의 C-family language는 block-level scope를 사용한다. block-level scope란 code block({…})내에서 유효한 scope를 의미한다. 여기서 “유효하다”라는 것은 “참조(접근)할 수 있다”라는 뜻이다.

int main(void) {
  // block-level scope
  if (1) {
    int x = 5;
    printf("x = %d\n", x);
  }

  printf("x = %d\n", x); // use of undeclared identifier 'x'

  return 0;
}

위의 C언어 코드를 보면 if문 내에서 선언된 변수 x는 if문 코드 블럭 내에서만 유효하다. 즉, if문 코드 블럭 밖에서는 참조가 불가능하다.

하지만 자바스크립트는 function-level scope를 사용한다. function-level scope란 함수 코드 블럭 내에서 선언된 변수는 함수 코드 블럭 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.

단, ECMAScript 6에서 도입된 let keyword를 사용하면 block-level scope를 사용할 수 있다.

var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1

let y = 0;
{
  let y = 1;
  console.log(y); // 1
}
console.log(y);   // 0

1. Global scope

글로벌 영역에 변수를 선언하면 이 변수는 어느 곳에서든지 참조할 수 있는 global scope를 갖는 전역 변수가 된다. 전역 변수는 전역 객체(Global Object) window의 프로퍼티이다.

var global = 'global';

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

console.log(global);
console.log(local); // Uncaught ReferenceError: local is not defined

변수 global는 함수 영역 밖의 글로벌 영역에서 선언되었다. 자바스크립트는 타 언어와는 달리 특별한 시작점이 없어서 위 코드와 같이 글로벌 영역에 변수나 함수를 선언하기 쉽다.

C언어의 경우 main 함수가 시작점이 되기 때문에 대부분은 코드는 main 함수 내에 포함된다. C언어의 경우 전역 변수를 선언하기 위해서는 의도적으로 main 함수 밖에 변수를 선언하여야 한다.

#include <stdio.h>

/* global variable declaration */
int g;

int main () {

  // local variable declaration
  int a, b;

  // actual initialization
  a = 10;
  b = 20;
  g = a + b;

  printf ("value of a = %d, b = %d and g = %d\n", a, b, g);

  return 0;
}

하지만 자바스크립트는 다른 C-family language와는 달리 특별한 시작점이 없으며 코드가 나타나는 즉시 해석되고 실행된다. 따라서 글로벌 영역에 변수를 선언하기 쉬우며 이것는 전역 변수를 남발하게 하는 문제를 야기시킨다.

전역 변수의 사용은 변수명의 중복 등 여러 문제를 발생시키므로 가급적 사용을 억제하여야 한다.

2. Non block-level scope

if (true) {
  var x = 5; // The scope is inside the if-block
}
console.log(x);

변수 x는 코드 블럭 내에서 선언되었다. 하지만 자바스크립트는 block-level scope를 사용하지 않으므로 function 밖에서 선언된 변수는 코드 블럭 내에서 선언되었다할지라도 모두 global scope을 갖게된다. 따라서 변수 x는 전역 변수이다.

3. Function scope

var a = 10;     // 전역변수

(function () {
  var b = 20;   // 지역변수
})();

console.log(a); // 10
console.log(b); // "b" is not defined

자바스크립트는 function-level scope를 사용한다. 즉 함수 내에서 선언된 매개변수와 변수는 함수 외부에서는 유효하지 않다. 따라서 변수 b는 지역 변수이다.

var x = 'global';

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

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

전역변수 x와 지역변수 x가 중복 선언되었다. 전역 영역에서는 전역변수만이 참조 가능하고 함수 내 지역 영역에서는 전역과 지역 변수 모두 참조 가능하나 위 예제와 같이 변수명이 중복된 경우, 지역변수를 우선하여 참조한다.

다음은 함수 내에 존재하는 함수인 내부 함수의 경우를 살펴보자.

var x = 'global';

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

  function bar() {  // 내부함수
    console.log(x); // ?
  }

  bar();
}
foo();
console.log(x); // ?

내부함수는 자신을 포함하고 있는 외부함수의 변수에 접근할 수 있다. 이는 매우 유용하다. 클로저에서와 같이 내부함수가 더 오래 생존하는 경우, 타 언어와는 다른 움직임을 보인다.

함수 bar에서 참조하는 변수 x는 함수 foo에서 선언된 지역변수이다. 이는 실행 컨텍스트의 스코프 체인에 의해 참조 순위에서 전역변수 x가 뒤로 밀렸기 때문이다.

var x = 10;

function foo() {
  x = 100;
  console.log(x);
}
foo();
console.log(x); // ?

함수(지역) 영역에서 전역변수를 참조할 수 있으므로 전역변수의 값도 변경할 수 있다. 내부 함수의 경우, 전역변수는 물론 상위에 함수에서 선언한 변수에 접근/변경이 가능하다.

var x = 10;

function foo(){
  var x = 100;
  console.log(x);

  function bar(){   // 내부함수
    x = 1000;
    console.log(x); // ?
  }

  bar();
}
foo();
console.log(x); // ?

중첩 scope는 가장 인접한 지역을 우선하여 참조한다.

var foo = function ( ) {

  var a = 3, b = 5;

  var bar = function ( ) {
    var b = 7, c = 11;

// 이 시점에서 a는 3, b는 7, c는 11

    a += b + c;

// 이 시점에서 a는 21, b는 7, c는 11

  };

// 이 시점에서 a는 3, b는 5, c는 not defined

  bar( );

// 이 시점에서 a는 21, b는 5

};

4. 암묵적 전역 (implied globals)

아래의 foo 함수 내 변수 x에 1을 할당하였다. 하지만 foo 함수 내에는 변수 x가 존재하지 않는다. 따라서 상위 지역(아래 예제의 경우 전역)에서 변수 x를 찾고 존재하지 않으면 변수 x를 암묵적으로 전역변수로 선언한다.

function foo() {
  x = 1;   // Throws a ReferenceError in "use strict" mode
  var y = 2;
}

foo();

console.log(x); // 1
console.log(y); // ReferenceError: y is not defined

의도하지 않게 전역변수가 되었다면 혼란의 여지가 있으므로 var keyword는 반드시 사용하자.

5. Lexical scoping (Static scoping)

자바스크립트는 함수가 선언된 시점에서의 유효범위를 갖는다. 예제의 함수 bar가 어떤 상황에서 호출될 지 선언 시점에서는 알 수 없다.

var i = 5;

function foo() {
  var i = 10;
  bar();
}

function bar() { // 선언된 시점에서의 scope를 갖는다!
  console.log(i);
}

foo(); // ?

6. 변수명의 중복

2개의 파일로 분리된 자바스크립트 코드가 있다고 가정한다.

// x.js
function foo (){
  // var i = 0;
  i = 0;
  // ...
}

// y.js
for(var i = 0; i < 5; i++){
  foo();
  console.log(i);
}

x.js와 y.js에 모두 변수 i와 존재한다. 이는 의도되지 않은 것이다. HTML에서 이 2개의 자바스크립트 파일을 로드하여 사용하면 변수 i는 중복된다.

<!DOCTYPE html>
<html>
<body>
  <script src='x.js'></script>
  <script src='y.js'></script>
</body>
</html>

x.js의 변수 i는 var 키워드를 사용하지 않았으므로 암묵적으로 전역 변수화 되고 y.js의 변수 i는 전역변수이다. 이때 자바스크립트는 변수명의 중복을 허용하므로 어떠한 에러 메시지도 발생시키지 않는다. 따라서 무한 반복 상태에 빠지게 된다.

이와 같이 전역변수의 무분별한 사용은 무척 위험하다. 전역변수를 반드시 사용하여야 할 이유를 찾지 못한다면 지역변수를 사용하여야 한다. 변수의 범위인 스코프는 좁을수록 좋다.

코드가 길어지면 변수명의 중복이 발생하기 쉬워 예기치 못한 이상 동작의 원인이 되기 쉬우며, 전역변수는 지역변수보다 탐색에 걸리는 시간이 더 길다.(속도면에서 그리 큰 차이는 없지만 분명히 느리다.)

7. 최소한의 전역변수 사용

더글라스 크락포드의 제안:
전역변수 사용을 최소화하는 방법 중 하나는 애플리케이션에서 전역변수 사용을 위해 다음과 같이 전역변수 객체 하나를 만들어 사용하는 것이다.

var MYAPP = {};

MYAPP.student = {
  name: 'Lee',
  gender: 'male'
};

console.log(MYAPP.student.name);

8. 즉시실행함수를 이용한 전역변수 사용 억제

전역변수 사용을 억제하기 위해, 즉시 실행 함수(IIFE, Immediately-Invoked Function Expression)를 사용할 수 있다. 이 방법을 사용하면 전역변수를 만들지 않으므로 라이브러리 등에 자주 사용된다. 즉시 실행 함수는 즉시 실행되고 그 후 전역에서 바로 사라진다.

(function () {
  var MYAPP = {};

  MYAPP.student = {
    name: 'Lee',
    gender: 'male'
  };

  console.log(MYAPP.student.name);
}());

console.log(MYAPP.student.name);

Reference

Back to top
Close