[Javascript] 3. 스코프(Scope) 에 관하여

nudge411·2021년 7월 30일
0

Javascript

목록 보기
3/3

1. 스코프란?

  • 스코프(Scope, 유효범위)는 자바스크립트를 포함한 모든 프로그래밍 언어의 기본적인 개념이다.
// 전역
var x = 'global';

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

foo(); // ?
console.log(x); // ?
  • 스코프는 참조 대상 식별자를 찾아내기 위한 규칙이다. 자바스크립트는 이 규칙대로 식별자를 찾는다.
  • 위 예제에서 전역에 선언된 변수 x는 어디에든 참조할 수 있다.
  • 함수 foo 내에서 선언된 변수 x는 함수 foo 내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없다.
  • 스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다.

2. 스코프의 구분

  • 자바스크립트에서 스코프를 구분해보면 다음과 같이 2가지로 나눌 수 있다.
  • 전역 스코프 (Global scope): 코드 어디에서든지 참조할 수 있다.
  • 지역 스코프 (Local scope or Function-level scope): 함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.
  • 모든 변수는 스코프를 갖는다. 변수의 관점에서 스코프를 구분하면 다음과 같이 2가지로 나눌 수 있다.
  • 전역 변수 (Global variable): 전역에서 선언된 변수이며 어디에든 참조할 수 있다.
  • 지역 변수 (Local variable): 지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.
  • 변수는 선언 위치(전역 또는 지역)에 의해 스코프를 가지게 된다.

3. 자바스크립트 스코프의 특징

  • 자바스크립트의 스코프는 타 언어와는 다른 특징을 가지고 있다.
  • 대부분의 C-family language는 블록 레벨 스코프(block-level 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)를 따른다.
  • 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다.
  • 단, ECMAScript 6에서 도입된 let keyword를 사용하면 블록 레벨 스코프를 사용할 수 있다.
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

4. 전역 스코프(Global scope)

  • 전역에 변수를 선언하면 이 변수는 어디서든지 참조할 수 있는 전역 스코프를 갖는 전역 변수가 된다.
  • var 키워드로 선언한 전역 변수는 전역 객체(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
  • 자바스크립트는 타 언어와는 달리 특별한 시작점(Entry point)이 없어서 위 코드와 같이 전역에 변수나 함수를 선언하기 쉽다.
  • 따라서 전역에 변수를 선언하기 쉬우며 이것는 전역 변수를 남발하게 하는 문제를 야기시킨다.
  • 전역 변수의 사용은 변수 이름이 중복될 수 있고, 의도치 않은 재할당에 의한 상태 변화로 코드를 예측하기 어렵게 만드므로 사용을 억제하여야 한다.

5. 비 블록 레벨 스코프(Non block-level scope)

if (true) {
  var x = 5;
}
console.log(x);
  • 변수 x는 코드 블록 내에서 선언되었다.
  • 자바스크립트는 블록 레벨 스코프를 사용하지 않으므로 함수 밖에서 선언된 변수는 코드 블록 내에서 선언되었다할지라도 모두 전역 스코프을 갖게된다.
  • 따라서 변수 x는 전역 변수이다.

6. 함수 레벨 스코프(Function-level scope)

var a = 10;     // 전역변수

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

console.log(a); // 10
console.log(b); // "b" is not defined
  • 자바스크립트는 함수 레벨 스코프를 사용한다. 즉, 함수 내에서 선언된 매개변수와 변수는 함수 외부에서는 유효하지 않다. 따라서 변수 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); // local

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

  bar();
}
foo();
console.log(x); // global
  • 내부함수는 자신을 포함하고 있는 외부함수의 변수에 접근할 수 있다.
  • 이는 매우 유용하다. 클로저에서와 같이 내부함수가 더 오래 생존하는 경우, 타 언어와는 다른 움직임을 보인다.
  • 함수 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); // 100

  function bar(){   // 내부함수
    x = 1000;
    console.log(x); // 1000
  }
  
  bar();
  console.log(x); // 1000
}
foo();
console.log(x); // 10
  • 중첩 스코프는 가장 인접한 지역을 우선하여 참조한다.

    bar() 구문 아래있는 콘솔로 bar 함수가 실행된후 foo 의 지역변수 x가 1000으로 재할당 된것을 알수있다.

var foo = function ( ) {
  var a = 3, b = 5;
  var bar = function ( ) {
    var b = 7, c = 11;
// 이 시점에 3,7,11
    a += b + c;
// 이 시점에 21,7,11
  };
// 이 시점에 3,5,d
  bar( );
// 이 시점에 21,5,d
};
foo();

7. 렉시컬 스코프

var x = 1;

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

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

foo(); // 1
bar(); // 1
  • 동적 스코프(Dynamic scope) : 함수를 어디서 호출하였는지에 따라 상위 스코프를 결정하는 것
  • 렉시컬 스코프(Lexical scope): 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것
  • 렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.
  • 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.
  • 따라서 함수 bar의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x의 값 1을 두번 출력한다.

    어디서 호출 했는가는 전혀 중요하지않다. 어디에 선언되어 있느냐만 보기 때문이다.

8. 암묵적 전역

var x = 10; // 전역 변수

function foo () {
  console.log(y) //  y is not defined
  function bar() {
    y = 20;
    console.log(x + y);
  }
  bar();
  console.log(y) // 20
}
foo(); // 30
  • 선언하지 않은 식별자 y는 마치 선언된 변수처럼 동작한다.
  • 스코프 체인을 통해 선언된 변수인지 확인한다. 이때 foo 함수의 스코프와 전역 스코프 어디에서도 변수 y의 선언을 찾을 수 없으므로 참조 에러가 발생해야 하지만 자바스크립트 엔진은 y = 20을 window.y = 20으로 해석하여 프로퍼티를 동적 생성한다.
  • 결국 y는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작한다. 이러한 현상을 암묵적 전역(implicit global)이라 한다.

첫번째 콘솔 y는 not defined 에러가 나지만, bar 함수를 실행하고 나서 암묵적으로 window.y = 20 프로퍼티가 할당 되었다. 후에 두번째 콘솔에서는 전역변수 y를 20 을 출력한다.

// 전역 변수 x는 호이스팅이 발생한다.
console.log(x); // undefined
// 전역 변수가 아니라 단지 전역 프로퍼티인 y는 호이스팅이 발생하지 않는다.
console.log(y); // ReferenceError: y is not defined

var x = 10; // 전역 변수

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

foo(); // 30
  • 하지만 y는 변수 선언없이 단지 전역 객체의 프로퍼티로 추가되었을 뿐이다. 따라서 y는 변수가 아니다.
  • 따라서 변수가 아닌 y는 변수 호이스팅이 발생하지 않는다.
var x = 10; // 전역 변수

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

foo(); // 30

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

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

console.log(window.x); // 10
console.log(window.y); // undefined
  • 또한 변수가 아니라 단지 프로퍼티인 y는 delete 연산자로 삭제할 수 있다. 전역 변수는 프로퍼티이지만 delete 연산자로 삭제할 수 없다.

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

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

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

console.log(MYAPP.student.name);

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

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

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

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

console.log(MYAPP.student.name);
profile
잊기 위한 기록을 합니다.

0개의 댓글