[JavaScript] 스코프와 스코프 체인, 클로저란?

HyungJin Han·2023년 2월 20일
0

JavaScript

목록 보기
5/7
post-thumbnail

1. Scope (유효범위, 스코프)

유효범위(Scope)는 JavaScript 뿐만 아니라 모든 프로그래밍 언어에서 가장 기본적인 개념 중 하나로 반드시 알고 넘어가야 한다.

하지만 JavaScript의 유효범위는 다른 언어의 유효범위와 다르다.

프로그래밍 언어에서 유효범위는 어느 범위까지 참조하는 지를 뜻한다.

  • 전역 스코프 (Global Scope)

    • 스크립트 전체에서 참조되는 것을 의미하며, 어느 곳에서든 참조된다.
  • 지역 스코프 (Local Scope)

    • 정의된 함수 내에서만 참조되는 것을 의미하며, 밖에서는 참조되지 않는다.

1-1. Scope(유효범위)의 특징

  • 함수 단위의 유효범위 (function-level-scope)

  • 변수명 중복 허용

  • 암묵적 선언 (Implied Globals)

  • Lexical Scoping (Static Scoping)

1-1-1. function-level-scope

function-level-scope란?

함수 코드 블럭 내에서 선언된 변수는 함수 코드 블럭 내에서만 유효하고, 함수 외부에서는 유효하지 않다는 것이다.

var x = 0;

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

console.log(x); // 1 - 결론적으로 var로 선언한 x가 값이 1로 변경

let y = 0;

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

console.log(y); // 0 - 결론적으로 let로 선언한 y는 함수 내부에서만 1

1-1-2. 변수명 중복 허용

클로벌 영역에 변수를 선언하면, 이 변수는 어느 곳에서든지 참조할 수 있는 Global Scope 를 갖는 전역 변수가 된다.

var global = 'global';

function foo() {
  var local = 'local';

  console.log(global); // global
  // Global 영역의 변수로 어디서든 사용 가능
  console.log(local); // local
}

foo();

console.log(global); // global
console.log(local); // Uncaught ReferenceError: local is not defined
// Local 영역의 변수로 함수 내에서만 사용 가능

1-1-3. 암묵적 전역 (Implied Globals)

명시적으로 변수 앞에 var를 붙여주지 않으면 암묵적 전역 변수가 된다.

하지만 암묵적 전역은 오류를 발생시키는 원인이 될 가능성이 크기 때문에, 반드시 var, let, const 키워드를 사용하여 변수를 선언한 다음 사용해야 한다.

하지만 오타나 문법지식의 미비로 인한 실수는 언제나 발생할 수 있기 때문에, 이를 위해 ES5 부터 Strict Mode(엄격 모드)가 추가되었으며, ES6에서 도입된 클래스와 모듈은 기본적으로 Strict Mode가 적용된다.

Strice Mode(엄격 모드)란?

Strice Mode(엄격 모드)는 JavaScript 언어의 문법을 좀 더 엄격히 적용하여 오류를 발생시킬 가능성이 높거나 자바스크립트 엔진의 최적화 작업에 문제가 될 수 있는 코드에 대해 명시적 에러를 발생시킨다.

ESLint와 같은 도구를 사용해도 Strice Mode와 유사한 효과를 얻을 수 있다.

ESLint 도구는 정적 분석(Static Analysis)기능을 통해 소스코드를 실행하기 전에 소스코드를 스캔하여 문법적 오류만 아니라 잠재적 오류까지 찾아내고 오류의 원인을 리포팅해주는 유용한 도구다.

ESLint 도구는 Strice Mode가 제한하는 오류는 물론 코딩 컨벤션을 설정 파일 형태로 정의하고 강제할 수 있기 때문에 더욱 강력한 효과를 얻을 수 있다.

function foo() {
  x = 'x'; // ES6의 Strinc Mode 및 ESLint로 인해 오류 출력
  var y = 'y';
}

foo();

console.log(x); // x
console.log(y); // Uncaught ReferenceError: y is not defined
// Local 영역의 변수로 함수 내에서만 사용 가능

1-1-4. Lexical Scoping (Static Scoping)

자바스크립트는 함수가 선언된 시점에서의 유효범위를 갖는다.

  • 예제 1

    var number = 1234;
    
    function printNumber() {
      console.log(number);
    }
    
    function wrapper() {
      number = 4321;
      // printNumber 함수를 실행시키기 전에 number를 변경했기 때문에 값이 변경됨
      printNumber();
    }
    
    wrapper(); // 4321

    printNumber 함수를 실행하기 직전에 number 의 값을 변경해주었으므로 wrapper 함수를 실행시켰을 때에 값이 변경되어, 1234에서 4321가 출력된다.

  • 예제 2

    var number = 1234;
    
    function printNumber() {
      console.log(number);
    }
    
    function wrapper() {
      var number = 4321;
      console.log(number); // 4321
      printNumber();
    }
    wrapper(); // 1234

    wrapper 함수 안에서 var 키워드를 사용하게 되면 결과가 달라진다.

    함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)있는 변수를 계속 참조하게 된다.

    위에 예시에서는 printNumber 함수 안의 number 변수는 선언 시 가장 가까운 전역변수 number를 참조하게 된다.


2. 스코프체인 (Scope Chain)

새롭게 정의된 스코프는 상위의 스코프에 접근할 수 있다.

스코프 체인은 Scope의 가장 내부에서 Scope Chain을 따라 바깥쪽으로 검색을 하게 된다.


3. 클로저 (Closure)

클로저는 함수가 선언된 환경의 (Lexical) 스코프를 기억하여, 함수가 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기술이다.

const counter = () => {
  let count = 0;

  // changeCounter는 inner 함수
  // 객체를 리턴하고 있고, 객체 안에는 increase, decrease, show라는 inner 함수들을 저장
  function changeCount(number) {
    count += number;
  }

  return {
    increase: function () {
      changeCount(100);
    },
    decrease: function () {
      changeCount(-10);
    },
    show: function () {
      console.log(count);
    }
  }
};

const counterClosure = counter();
// counter를 실행하면, outer 함수 스코프를 기억하고 있는 클로저들이 담긴 객체를 반환
// counterClosure는 counter 함수 내부에 정의된 count나 changeCount에 접근 가능

counterClosure.increase();
counterClosure.show(); // 100

counterClosure.decrease();
counterClosure.show(); // 90

내부 함수는 외부 함수의 지역 변수에 접근 할 수 있는데, 외부 함수의 실행이 끝나서 외부 함수가 소멸된 이후에도 내부 함수가 외부 함수의 변수에 접근할 수 있다.

이러한 매커니즘을 클로저(Closure)라고 한다.

3-1. React Hook에서의 클로저(Closure)

3-1-1. useState의 작동 방식

React에서 함수형 컴포넌트의 상태 관리를 위해서는 컴포넌트 외부에 저장된 값을 사용하며 클로저(Closure)를 통해 해당 값에 접근해서 상태를 비교하고 변경한다.

useState는 컴포넌트 내부에서 값을 변경시키는 것이 아니라, 외부에 있는 값을 변경시키기 때문에 상태가 변경된 직후 컴포넌트가 가진 값은 이전의 값을 그대로 참조한다.

각 컴포넌트의 상태 정보는 배열 형태로 저장되기 때문에 상태를 변화시키는 hook을 조건문이나 반복문 안에서 사용하면 잘못된 순서의 값을 참조하게 될 수 있다.


참고 사이트

aeong98.log - 스코프(Scope) 와 클로저(Closure)의 이해
암묵적 전역(Implicit Global), Strict Mode

profile
토끼보다는 거북이처럼 꾸준하게

0개의 댓글