게으른개발너D

스코프(Scope)와 클로저(Closure) 본문

개발/JavaScript

스코프(Scope)와 클로저(Closure)

lazyhysong 2023. 8. 4. 16:42

✨ 스코프

유효 범위라고도 부르며 변수가 어느 범위까지 참조되는 지를 뜻한다.

어디서도 접근 가능한 전역 스코프(Global Scope)가 있고, 해당 스코프 컨텍스트 안에서만 접근 가능한 지역 스코프(Local Scope)로 나뉜다.

const a = 5; // Global Scope
{
  const b = 3; // Local Scope
  console.log(a, b); // 5, 3
}
console.log(a, b); // Error!

 

var를 사용하면 개발자가 예상치 못한 오류가 생길 수 있다.

블록 내부에 새롭게 선언하더라도 블록 외부 값도 변하게 된다.

var a = 5;
{
  // 호이스팅 되어 변수 선언이 상단으로 올라가버린다.
  var a = 10;
  console.log(a); // 10
}
console.log(a); // 10

 

var은 함수 수준의 scope이고 const와 let은 블록 수준의 scope이기 때문이다.

 


✨ 클로저 (Closure)

함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법

function makeGreeting(name) {
  const greeting = "Hello, ";
  
  return function () {
    console.log(greeting + name);
  };
}

const world = makeGreeting("World!");
const hwayeon = makeGreeting("Hwa-Yeon");

world();
hwayeon();

이는 반환된 함수가 greeting 함수를 계속 참조하고 있어, 메모리에서 사라지지 않기 때문이다.

 

이처럼 클로저는 닫힘, 폐쇄라는 뜻을 가지고 있는데, 더이상 외부에서 접근이 불가능한 영역을 클로저를 통해서만 접근하는 모습이 폐쇄된 모습과 유사하다고 볼 수 있다.

 

이런 클로저를 유익하게 사용할 수 있는 방법 중 하나가 은닉화이다.

 

은닉화

클로저를 이용하여 내부 변수와 함수를 숨길 수 있다.

function Counter() {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    },
  };
}

const counter = Counter();

console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
couner.decrement();
console.log(counter.value()); // 1

코드의 Counter 함수를 보면 내부의 변수와 함수는 외부에서 접근이 불가능하다.

따라서 반환된 함수들로만 값을 조작할 수 있게 된다.

이런식으로 내부 변수와 함수를 숨기면서 개발 실수를 줄여나갈 수 있다.

 

클로저를 잘 알아야하는 이유는 유용하게 사용하기보단 알기 힘든 버그를 잘 수정하기 위해서이다.

 

예를 들어보자.

function counting() {
  let i = 0;
  for (i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 100);
  }
}

counting();

위 코드의 출력 결과는 어떻게 될까?

 

결과는 55555

 

보통 1, 2, 3, 4, 5가 순차적으로 나올 거라 예상할 것이다.

setTimeout의 대시기간이 끝나 콜백 함수가 실행되는 시점에는 이미 루프가 종료되어, i가 5가 되었기 때문이다.

 

이를 해결할 수 있는 방법은 두 가지가 있다.

 

1. IIFE (즉시 실행 함수) 이용

function counting() {
  let i = 0;
  for (i = 0; i < 5; i++) {
    (function (number) {
      setTimeout(function() {
        console.log(number);
      }, number * 100);
    })(i);
  }
}

counting();

루프마다 클로저를 만드는 방법이다.

 

2. let 이용

let은 블록 수준 스코프이기 때문에 매 루프마다 클로저가 새롭게 생성된다.

function counting() {
  for (let i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
    }, i * 100);
  }
}

counting();

 

 

 

 

'개발 > JavaScript' 카테고리의 다른 글

jest - 설치 및 튜토리얼  (0) 2023.10.23
[test] js test code 작성  (0) 2023.08.07
흐름제어 (Control Flow, Data Flow)  (0) 2023.08.04
표현식과 연산자 (Expressions and Operators)  (0) 2023.08.03
Javascript Memory  (0) 2023.08.03
Comments