클로저(Closure)

jYur·2023년 3월 2일
0

보통의 코드

let count = 0;
function increment() {  
}

함수 스코프({}) 밖에 변수가 하나 있네요.
당연하게도, 아래처럼 increment 함수 안에서 count 변수에 접근할 수 있습니다.

let count = 0;
function increment() {
  count = count + 1;
  console.log(`count의 값은 ${count}`);
}
increment(); // 출력: count의 값은 1
increment(); // 출력: count의 값은 2

increment()가 잘 작동하네요.

그런데,
count는 다른 함수들에서도 접근할 수 있죠.
그러다가 count 값이 도중에 변경되면?

increment(); // 출력: count의 값은 1
count = 100;
increment(); // 출력: count의 값은 101

increment()가 호출될 때만 count1씩 증가하는 게 의도였는데 어긋났어요.

둘을 좀 더 강하게 엮을 순 없을까요?
increment()count에 접근할 수 있도록 말이에요.

클로저를 만드는 코드

있습니다.
increment() 함수를 반환하는 새로운 함수를 만들면 됩니다.

function counter() {

  let count = 0;
  function increment() {
    count = count + 1;
    console.log(`count의 값은 ${count}`);
  }
  
  return increment;
}

increment() 함수를 반환하는 새로운 함수 counter()를 만들었어요.
좀 전에 봤던 countincrement() 코드가 그대로 counter() 함수 안에 있고
counter() 함수는 그냥 increment() 함수를 반환합니다.

그럼 한번 counter()한테서 increment()를 받아 사용해 볼까요?

const increment = counter();
increment(); // 출력: count의 값은 1
increment(); // 출력: count의 값은 2

increment() 함수만 반환받아 사용했는데 count의 값이 정상적으로 변하네요.
이제 count 값을 마음대로 변경할 수 없어요. countcounter() 함수 안에 있어서 외부에서 접근할 수 없거든요.

그런데 countcounter() 함수 스코프에서 만들어지는 변수라면,
첫 번째 줄에서 counter() 함수 실행이 끝났을 때 사라져야 하는 거 아닌가요?

우린 방금 클로저가 만들어지도록 코딩한 겁니다!
counter() 함수를 호출할 때 클로저가 만들어집니다.
클로저가 만들어지면 위처럼 counter() 함수 실행이 끝난 뒤에도 countincrement()가 계속 접근할 수 있어요.

이렇게 우리는 클로저를 활용해서 increment() 함수를 통해서만 count에 접근할 수 있도록 감춰버렸어요.
C++나 Java 등의 클래스에서의 private 같기도 하네요.

counter()에 아래처럼 기능을 추가해 볼 수도 있답니다.

function counter() {
  let count = 0;
  function increment() {
    count = count + 1;
    console.log(`count: ${count}`);
  }
  
  function decrement() {
    count = count - 1;
    console.log(`count: ${count}`);
  }
  
  return {
    increment,
    decrement
  };
}

클로저 활용

먼저 다음과 같은 데이터가 있습니다.

const people = [
  { id: 1, name: '마석도', email: 'ma@gmail.com' },
  { id: 4, name: '이지안', email: 'lee@gmail.com' },
  { id: 6, name: '박새로이', email: 'park@gmail.com' }
];

people.filter()를 사용하여 특정한 사람을 제외한 데이터를 구하고 싶다면,

people.filter((person) => person.name !== '박새로이');
people.filter((person) => person.name !== '마석도');

보통 위처럼 코드를 작성합니다.
filter()에 전달하는 함수(predicate)를 클로저를 활용해서 추상화해봅시다. (Currying)

1단계

const excludePersonWithName = (뺄사람이름) => (person) => person.name !== 뺄사람이름;
people.filter(excludePersonWithName('박새로이'));
people.filter(excludePersonWithName('마석도'));

const excludePersonWithEmail = (email) => (person) => person.email !== email;
people.filter(excludePersonWithEmail('park@gmail.com'));
people.filter(excludePersonWithEmail('ma@gmail.com'));

2단계

const excludePersonWith = (propertyName) => (propertyValue) => (person) =>
  person[propertyName] !== propertyValue;

const excludePersonWithName = excludePersonWith('name');
people.filter(excludePersonWithName('박새로이'));
people.filter(excludePersonWith('name')('마석도'));

const excludePersonWithEmail = excludePersonWith('email');
people.filter(excludePersonWithEmail('park@gmail.com'));
people.filter(excludePersonWith('email')('ma@gmail.com'));

0개의 댓글