Frontend/JavaScript

[js] 스코프와 클로저

양원준 2024. 6. 12. 15:58
728x90

 

먼저, 클로저에 대해 살펴보기 전에 스코프에 대한 개념을 간단히 정리해보자

 

스코프(scope)

스코프는 변수가 접근할 수 있는 범위를 뜻한다. 자바스크립트에서는 전역 스코프와 지역 스코프가 있다.

 

전역 스코프

전역 스코프에 선언된 변수는 코드 어디서든 접근 가능하다. 전역 스코프에 변수를 선언하려면 함수나 블록 외부에서 선언하면 된다.

var globalVar = 'I am global'; // 전역 변수

function globalExample() {
  console.log(globalVar); // 'I am global'
}
globalExample();
console.log(globalVar); // 'I am global'

 

지역 스코프

js에서 지역 스코프는 블록 스코프와 함수 스코프로 나뉘어진다.

보통의 경우 함수 스코프는 없지만(js에만 있는 듯?하다 거의)  js에서 var를 사용하면 이는 함수 스코프를 가진다. ES6에서 letconst 키워드로 도입하여 사용할 수 있게 되었고, 중괄호 {}로 둘러싸인 코드 블록 내에서 유효하다.

//함수 스코프
function localScopeExample() {
  var localVar = 'I am local';
  console.log(localVar); // 'I am local'
}
localScopeExample();
console.log(localVar); // ReferenceError: localVar is not defined


//블록 스코프
{
  let blockScopedVar = 'I am block scoped';
  console.log(blockScopedVar); // 'I am block scoped'
}
console.log(blockScopedVar); // ReferenceError: blockScopedVar is not defined

 

 

 

아래 예제를 보면 함수가 호출되는 위치가 아니라 함수가 선언되는 위치에 기반하여 상위 스코프가 결정되는 것을 알수 있다. 이를 렉시컬 스코프(정적 스코프) 라고 한다.

var x = 1;

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

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

func1(); // 1
func2(); // 1

 

렉시컬 스코프는 함수를 어디에 선언하였는지에 따라 상위 스코프가 결정되는 것을 말한다. 함수가 호출되는 위치가 아니라, 함수가 선언된 위치를 기준으로 스코프 체인이 결정되는 것

 

대부분의 프로그래밍 언어는 렉시컬 스코프를 따르지만, Bash 같은 쉘 스크립트 언어는 함수가 호출되는 위치를 따르는 동적스코프를 따른다고 한다. 정리하자면,

  • 렉시컬 스코프(정적 스코프): 함수가 선언된 위치를 기준으로 스코프 체인이 결정
  • 동적 스코프: 함수가 호출되는 위치에 따라 상위 스코프가 결정

 

클로저

클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합이다. 함수가 자신이 선언된 스코프를 기억하고, 그 스코프에 접근할 수 있는 기능을 제공한다.

 

쉽게 말하여 클로저는 다음과 같다.

  • 자신이 선언된 당시의 환경(==렉시컬 스코프)을 기억하는 것
  • 자신이 생성될 때의 스코프에 있는 변수들을 참조할 수 있는 것

 

클로져 예제

function outerFunc() {
  let outerVar = 'I am outside!';

  function innerFunc() {
    console.log(outerVar); // 'I am outside!'
  }

  return innerFunc;
}

const myClosure = outerFunc();
myClosure(); // 'I am outside!'

 

1. outerFunc

  • outerFunc은 outerVar 변수를 선언하고, innerFunc을 정의
  • innerFunc은 outerVar를 참조
  • outerFunc은 innerFunc을 반환

2. 클로저 형성

  • const myClosure = outerFunc(); 호출 시, outerFunc은 실행되어 innerFunc을 반환
  • 반환된 innerFunc은 outerFunc의 스코프를 기억
  • myClosure는 이제 innerFunc을 참조하며, outerVar에 접근

3. 클로저의 활용

  • 클로저를 통해 함수가 생성된 환경을 기억하고, 나중에 호출될 때 그 환경에 있는 변수들을 참조

 

사실, 프로그래밍을 조금 하다보면 명확히 아는 개념은 아니지만 자연스럽게 생각하던 사고 방식이다.

클로저를 이용해 정보은닉과 캡슐화도 가능하다

 

클로저 정보은닉 예제

function createCounter() {
  let count = 0; // 비공개 변수

  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (비공개 변수에 접근 불가)

 

간단한 카운터 예제이다. 

  • count 변수를 선언하고, 이는 함수 외부에서 직접 접근할 수 없다.
  • 그에 반해 다른 메서드들은 클로저를 통해 count 변수에 접근하여 값을 변경하거나 반환 가능하다.

count 변수가 비공개 변수라고 가정했을 때, 정보은닉과 캡슐화 무결성을 지킬 수 있다.

 

 

 

728x90