Scope in JavaScript

4 분 소요


0. 들어가면서

스코프(scope)는 모든 프로그래밍 언어에서 중요한 개념입니다. JavaScript의 스코프는 다른 언어들과 조금 다른 특징들이 존재합니다. 이번 포스트에서 이에 관련된 내용들을 정리하였습니다.

1. Scope

모던 자바스크립트 Deep Dive 자바스크립트의 기본 개념과 동작 원리
스코프는 식별자가 유효한 범위를 말한다.

JavaScript에서 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)들은 자신이 선언된 위치에 따라 유효한 범위가 결정됩니다. 유효한 범위란 선언된 식별자를 다른 코드가 참조할 수 있는지에 대한 여부를 의미합니다.

1.1. What is valid scope?

간단한 예제 코드를 통해 확인해보겠습니다. 예제 코드는 브라우저 개발자 도구(F12) 콘솔에서 실행할 수 있습니다. 아래 코드를 실행하면 다음과 같은 결과를 얻습니다.

  • foo 변수는 전역 스코프(global scope)에 선언합니다.
  • bar 변수는 baz 함수 내 지역 스코프(local scope)에 선언합니다.
  • 전역에서 console.log(...) 함수를 실행합니다.
    • foo 변수는 정상적으로 “Hello” 문자열이 출력됩니다.
    • bar 변수는 ReferenceError가 발생합니다.
  • foo 변수는 전역에서 유효합니다.
  • bar 변수는 전역에서 유효하지 않습니다.
var foo = "Hello";

function baz() {
    var bar = "World";
}

console.log(foo); // Hello
console.log(bar); // Uncaught ReferenceError: bar is not defined

1.2. Why do we need scope?

ES6(ECMAScript6) 모듈이 등장하기 전 JavaScript는 파일이 분리되어 있더라도 하나의 전역 스코프를 공유하기 때문에 네임스페이스(namespace) 오염이 발생하는 문제점이 있었습니다. 스코프 개념이 없었다면 함수 내 동일한 이름을 가지는 식별자들까지도 충돌이 발생하고, 이로 인해 의도치 않은 동작이 일어날 수도 있습니다.

아래는 동일한 이름을 가진 식별자를 다루는 예시 코드입니다. JavaScript 엔진은 식별자 결정(identifier resolution)을 통해 같은 이름의 변수들 중에 어떤 변수를 참조할지 결정합니다.

  • foo 변수를 전역 스코프에 선언합니다.
  • foo 변수를 bar 함수 내 지역 스코프에 선언합니다.
  • bar 함수 실행 시 foo 변수는 “World” 입니다.
  • 전역에서 foo 변수는 “Hello” 입니다.
var foo = "Hello";

function bar() {
    var foo = "World";
    console.log(foo); // World
}

bar();

console.log(foo); // Hello

2. Types of Scope

JavaScript 스코프는 전역(global)과 지역(local)로 나눌 수 있습니다. 예제 코드와 함께 각 스코프의 특징을 살펴보겠습니다.

  • 변수 x는 전역 스코프에 속합니다.
    • 전역은 코드 가장 바깥 영역이며 전역 스코프를 만듭니다.
    • 전역 스코프에 변수를 선언하면 전역 변수가 되며 어디서든 참조할 수 있습니다.
    • 브라우저에서 코드를 실행하면 전역 변수들은 window 객체를 통해 참조할 수 있습니다.
  • 변수 y, x는 지역 스코프에 속합니다.
    • 지역은 함수 몸체 내부 영역이며 지역 스코프를 만듭니다.
    • 지역 스코프에 변수를 선언하면 지역 변수가 됩니다.
    • 지역 변수는 자신이 선언된 지역과 하위 지역에서만 참조할 수 있습니다.
      • 지역 변수 y는 자신이 선언된 하위 영역인 bar 함수 내부에서 참조할 수 있습니다.
      • 지역 변수 y를 전역에서 참조하는 경우 ReferenceError가 발생합니다.
var x = "global";

function foo() {
    var y = "local in foo";

    console.log("foo", x); // foo global
    console.log("foo", y); // foo local in foo

    function bar() {
        var x = "local in bar";

        console.log("bar", x); // bar local in bar
        console.log("bar", y); // bar local in foo
    }

    bar();
}

foo();

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

3. Levels of Local Scope

ES5까지 JavaScript의 지역 스코프는 함수 레벨에서만 유효했습니다. ES6부터 등장한 변수 선언 키워드인 let, const를 사용하면 블록 레벨 스코프가 가능합니다.

3.1. Function Level Scope

예시 코드를 통해 함수 레벨 스코프를 살펴보겠습니다.

  • var 키워드로 선언한 변수는 함수 코드 블럭 내에서만 지역 스코프에 포함됩니다.
  • if 블럭에서 선언한 foo 변수는 지역 스코프로써 유효하지 않으므로 전역 변수로 취급되며 전역에서 정상적으로 출력됩니다.
  • bar 함수 내에서 선언한 baz 변수는 지역 스코프에 선언되었으므로 지역 변수로 취급되며 전역에서 호출시 에러가 발생합니다.
var empty = "";
if (!empty) {
    var foo = "Hello";
}

function bar() {
    var baz = "World";
}

console.log(foo); // Hello
console.log(baz); // Uncaught ReferenceError: baz is not defined

3.2. Block Level Scope

다음 예시 코드를 통해 let, const 키워드로 선언한 변수가 블록 레벨(block level) 스코프에서 유효한지 확인 가능합니다.

  • let, const 키워드로 선언한 변수는 코드 블럭 내에 있다면 지역 스코프로 포함됩니다.
  • if 블럭, for 반복문, while 구문, try-catch 구문 모두 동일하게 적용됩니다.
var empty = "";

if (!empty) {
    let foo = "Hello";
}

console.log(foo); // Uncaught ReferenceError: foo is not defined
try {
    const foo = "Hello";
} catch (e) {
    console.log(e);
}

console.log(foo); // Uncaught ReferenceError: foo is not defined
for(let index = 0; index < 5; index++) {
    console.log(index); // 0, 1, 2, 3, 4
}

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

4. How to deciside scope?

프로그래밍 언어마다 스코프를 정의하는 방법이 조금씩 다릅니다. 어떤 방식이 정리하면서 JavaScript는 어떤 방식을 따르는지 살펴보겠습니다.

4.1. Lexical Scope

Programming Languages Lecture 9 - Lexical Scope, Closures
Lexical scope: use environment where function [and variable] is defined

JavaJavaScript는 모두 렉시컬(lexcial) 스코프 방식을 사용합니다. 렉시컬 스코프 방식은 함수가 선언된 위치의 컨텍스트를 사용하여 스코프를 정의합니다. 아래 예시 코드를 통해 확인해보겠습니다.

  • baz 함수가 선언된 위치는 전역입니다.
  • baz 함수 내부에서 사용한 foo 변수는 전역 스코프를 참조합니다.
  • bar 함수 내부에서 baz 함수가 호출되면 전역 스코프의 foo 변수 값인 “Hello”가 출력됩니다.
  • 만약, 다이나믹 스코프 방식을 따른다면 bar 함수 내에서 baz 함수가 실행되므로 “World”가 출력되어야 합니다.
var foo = "Hello";

function baz() {
    console.log(foo); // Hello
}

function bar() {
    var foo = "World";
    baz();
}

bar();

4.2. Dynamic Scope

Programming Languages Lecture 9 - Lexical Scope, Closures
Dynamic scope: use environment where function [and variable] is called

Perl, Bash Shell 같은 오래된 언어들이 사용하는 방식입니다. 다이나믹 스코프 방식은 함수가 실행된 위치의 컨텍스트를 사용하여 스코프를 정의합니다. 다음과 같은 쉘(shell) 스크립트를 통해 확인해보았습니다.

  • baz 함수가 선언된 위치는 전역입니다.
  • bar 함수 내부에서 baz 함수가 실행되면 baz 함수 내부의 foo 변수는 지역 스코프를 참조합니다.
  • “World”가 출력됩니다.
foo="Hello"

baz() {
     echo "$foo" # World
}

bar() {
    foo="World"
    baz # baz call
}

bar # bar call

RECOMMEND NEXT POSTS

REFERENCE

카테고리:

업데이트:

댓글남기기