[Javascript] 스코프(scope)

js scope

Posted by dongjune on September 20, 2021

📌스코프란?

스코프는 프로그래밍 언어의 기본적인 개념이므로 확실한 이해가 필요합니다. 이번 포스팅에서 스코프에 대해 자세히 알아보도록 하겠습니다.

스코프란 참조대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙입니다. 자바스크립트는 이 규칙으로 식별자를 탐색합니다.

위 설명으론 감이 잘 안오니 다음의 예제를 보겠습니다.

1
2
3
4
5
6
7
8
9
var x = 'global';

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

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

변수 x가 두번 선언되고 있습니다. 자바스크립트는 이 두 변수를 어떻게 구별할 까요?

foo 함수안에 선언된 x는 함수 내부에서만 참조할 수 있고, 전역에서 선언된 x는 전역에서 참조할 수 있습니다. 이처럼 같은 이름의 변수를 그 변수가 어디에서 선언됐는지에 따라 구별하고 있습니다. 이러한 규칙을 스코프라고 합니다.

만일 스코프가 존재하지 않는다면 시스템 전체에서 같은 이름의 식별자를 하나밖에 사용할 수 없게 됩니다. 시스템이 작다면 매번 내가 사용한 변수명을 확인해가며 어느정도 개발이 가능하겠지만 시스템이 커질수록 변수명이 중복되는 일은 빈번히 발생하게 되겠죠. 스코프가 이러한 식별자 충돌을 방지해줍니다.

📌스코프의 종류

자바스크립트의 스코프는 다음 2가지로 나눌 수 있습니다.

전역 스코프 (Global scope)

  • 코드 어디에서든 참조할 수 있다.

지역 스코프 (Local scope 또는 Function-level scope)

  • 함수 코드 블록으로 만들어지며 함수 자신과 하위 함수에서만 참조가능하다.

이러한 관점에서 변수도 2가지로 구분할 수 있습니다.

전역 변수 (Global variable)

  • 전역에서 선언된 변수, 어디에서든 참조 가능

지역 변수 (Local variable)

  • 지역(함수) 내에서 선언 된 변수, 그 지역과 하위 지역에서만 참조 가능

변수는 선언 위치에 의해 스코프를 갖게 됩니다. 즉 전역에서 선언된 변수는 전역 스코프를 갖고, 지역에서 선언된 변수는 지역 스코프를 갖게 됩니다.

📌자바스크립트의 스코프

대부분의 C 계열 언어들이 블록 레벨 스코프를 갖는 것과 달리 자바스크립트는 함수 레벨 스코프를 따릅니다.

블록 레벨 스코프는 {} 내에서 유효한 스코프를 의미하며 함수 레벨 스코프는 함수의 코드 블록 내에서 유효한 스코프를 의미합니다.

단 ES6에서 도입된 let 키워드를 사용하여 블록 레벨 스코프를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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

📌전역 스코프

var 키워드로 전역에 변수를 선언하면 그 변수는 전역 객체 window의 프로퍼티가 됩니다.

1
2
3
var hi = "hi";

console.log(window.hi) // "hi"

자바스크리브에는 C나 Java와 같은 다른 언어와 달리 엔트리 포인트가 존재하지 않기 때문에 전역에 변수나 함수를 선언하기 쉽습니다.

자바스크립트에서는 전역변수 선언이 쉬운 만큼 전역변수 사용을 남발하게 됩니다. 이는 의도치 않은 변수의 상태변화를 야기하기 쉬우므로 최대한 자제해야 합니다.

📌함수 레벨 스코프

1
2
3
4
5
6
7
8
var a = 10;     // 전역변수

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

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

자바스크립트는 함수 레벨 스코프를 사용합니다. 위의 예제와 같이 변수 b는 함수 안의 지역 스코프를 갖기 때문에 전역에서 참조할 수 없습니다.

🧐 지역 스코프와 전역 스코프를 모두 참조할 수 있는 경우

1
2
3
4
5
6
7
8
9
var x = 'global';

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

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

함수 foo 내부에서는 그곳의 지역 스코프와 전역 스코프 모두를 참조할 수 있습니다.

이 경우에는 지역 변수를 우선적으로 참조하게 되서 foo()local을 출력하게 됩니다.

🧐 중첩스코프의 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var x = 'global';

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

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

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

foo 함수 내부에 bar 함수가 선언됐고 이 bar함수는 foo의 지역 스코프와 전역 스코프를 참조할 수 있는 상태입니다.

이러한 경우에는 가장 인접한 스코프를 우선하여 참조하게 됩니다.

따라서 bar 내부에서 console.log(x)를 실행하면 bar의 지역 스코프를 참조한 후 foo의 지역 스코프를 참조하게 되어 local이 출력됩니다.

📌렉시컬 스코프 (Lexical scope)

프로그래밍 언어에는 상위 스코프를 결정하는 두 가지 방식이 존재합니다. 하나는 동적 스코프, 다른 하나는 렉시컬 스코프입니다. 자바스크립트는 이 중에서 렉시컬 스코프를 따르는 언어입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var x = 1;

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

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?

🛵동적 스코프

  • 함수가 어디에서 호출됐는지에 따라 상위 스코프를 결정
  • 위 예제에서 bar의 호출이 foo 안에서 일어나므로 bar는 foo의 지역스코프를 참조하게 된다.
  • 따라서 자바스크립트가 동적 스코프를 따른다면 10, 1이 출력된다.

🛵렉시컬 스코프

  • 함수가 어디에서 선언됐는지에 따라 상위 스코프를 결정
  • 위에서 bar의 선언이 전역에 존재하므로 bar는 전역 스코프를 참조하게 된다.
  • 따라서 1,1 이 출력 된다.

📌암묵적 전역

1
2
3
4
5
6
7
8
9
var x = 10; // 전역 변수

function foo () {
  // 선언하지 않은 식별자
  y = 20;
  console.log(x + y);
}

foo(); // 30

위 예제에서 변수 y는 선언되지 않은 식별자 입니다. 하지만 자바스크립트는 에러를 발생시키지 않습니다.

자바스크립트 엔진은 y=20window.y=20으로 해석하여 프로퍼티를 동적 생성하고, 결국 y는 전역 객체의 프로퍼티가 되어 전역 변수처럼 동작합니다.

이러한 현상을 암묵적 전역(implicit global)이라 합니다.

또한 단지 프로퍼티인 y는 delete 연산자로 삭제할 수 있습니다. 반면에 전역 변수는 프로퍼티이지만 delete 연산자로 삭제할 수 없습니다.

1
2
3
4
5
6
7
8
console.log(window.x); // 10
console.log(window.y); // 20

delete x; // 전역 변수는 삭제되지 않는다.
delete y; // 프로퍼티는 삭제된다.

console.log(window.x); // 10
console.log(window.y); // undefined

📌최소한의 전역 변수 사용

전역 변수를 최소화 하는 방법을 2가지 정도만 알아보겠습니다.

1. 전역 변수 객체를 만들어 사용

이 방법은 더글라스 크록포드가 제안한 방식입니다. 전역변수 사용이 불가피 할 경우 다음과 같이 전역변수 객체 하나를 만들어서 사용할 수 있습니다.

1
2
3
4
5
6
7
8
var MYAPP = {};

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

console.log(MYAPP.student.name);

2. 즉시실행함수 사용

즉시 실행 함수는 즉시 실행 된 후 전역에서 바로 사라집니다.

1
2
3
4
5
6
7
8
9
10
11
12
(function () {
  var MYAPP = {};

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

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

console.log(MYAPP.student.name);

Reference