고차함수 & 일급 객체 & 클로저

Tori·2024년 11월 27일

JavaScript

목록 보기
6/7
post-thumbnail

poiemaweb 배열 고차 함수 참고
poiemaweb 일급 객체 참고



고차함수란

  • 고차함수(Higher order function)는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수를 말한다.
  • 고차함수는 인자로 받은 함수를 필요한 시점에 호출하거나 클로저를 생성하여 반환한다.
  • 자바스크립트의 함수는 일급 객체이므로 값처럼 인자로 전달할 수 있으며 반환할 수도 있다.

고차 함수는 외부 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에 기반을 두고 있다.

함수형 프로그래밍은 순수 함수(Pure functino, 외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수)와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.

조건문, 반복문은 로직의 흐름을 이해하기 어렵게하여 가독성을 해치고, 변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있기 때문이다.

함수형 프로그래밍은 순수 함수를 통해 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고, 프로그램의 안정성을 높이려는 노력의 한 방법이라고 할 수 있다.


자바스크립트는 고차 함수를 다수 지원하고 있다. 특히 Array 객체에는 매우 유용한 고차 함수를 제공한다.



Array 객체의 유용한 고차함수들 알아보기

✅ 원본 배열을 변경하는 메소드
✅ 원본 배열을 변경하지 않는 메소드










일급 객체 (First-class object)

컴퓨터 프로그래밍 언어 디자인에서 일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다.
보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다. _위키백과 일급객체 참고

자바스크립트의 함수는 일급 객체이므로 아래와 같은 특징이 있다.

  1. 무명의 리터럴로 표현이 가능하다.
  2. 변수나 자료 구조(객체, 배열 등)에 저장할 수 있다.
  3. 함수의 매개변수에 전달할 수 있다.
  4. 반환값으로 사용할 수 있다.










클로저(closure)

클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.

const x = 1;

function outerFunc() {
  const x = 10;
  
  function innerFunc() {
    console.log(x);
  }
  
  innerFunc();
}

outerFunc();

outerFunc 함수 내부에서 중첩 함수 innerFunc가 정의되고 호출되었다.
이때 중첩 함수 innerFunc의 상위 스코프는 외부 함수 outerFunc의 스코프다.
따라서 중첩 함수 innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFunc의 x변수에 접근할 수 있다.

만약 innerFunc 함수가 outerFunc 함수의 내부에서 정의된 중첩 함수가 아니라면 innerFunc 함수를 outerFunc 함수의 내부에서 호출한다 하더라도 outerFunc 함수의 변수에 접근할 수 없다.

이 같은 현상이 발생하는 이유는 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문이다.



렉시컬 스코프

자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다.
이를 렉시컬 스코프(정적 스코프)라 한다.

스코프의 실체는 실행 컨텍스트의 렉시컬 환경이다. 이 렉시컬 환경은 자신의 "외부 렉시컬 환경에 대한 참조"를 통해 상위 렉시컬 환경과 연결된다. 이것이 바로 스코프 체인이다.

렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.



클로저의 주요 특징

클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다. 상태가 의도치않게 변경되지 않도록 상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

1. 데이터 프라이버시

function counter() {
  let count = 0; // private 변수처럼 동작
  
  return {
    increase() { return ++count; },
    decrease() { return --count; },
    getCount() { return count; }
  };
}

const count1 = counter();
console.log(count1.increase()); // 1
console.log(count1.increase()); // 2
console.log(count1.decrease()); // 1

2. 상태 유지(캡슐화)

function createUser(name) {
  let greeting = `Hello, ${name}!`;
  
  return {
    sayHi() { console.log(greeting); },
    changeName(newName) {
      greeting = `Hello, ${newName}!`;
    }
  };
}

const user = createUser('Hoho');
user.sayHi(); // "Hello, Hoho!"
user.changeName('Koko');
user.sayHi(); // "Hello, Koko!"

3. 모듈 패턴

const calculator = (function () {
  let result = 0;
  
  return {
    add(n) {
      result += n;
      return this;
    },
    subtract(n) {
      result -= n;
      return this;
    },
    getResult() {
      return result;
    }
  };
})();

calculator.add(5).subtract(2);
console.log(calculator.getResult()); // 3



클로저의 실제 활용 사례

1. 이벤트 핸들러

function handleClick (message) {
  return function (event) {
    console.log(message, event.target);
  }
}

button.addEventListener('click', handleClick('Button clicked: '));

2. 커링 (Currying)

function multiply (num1) {
  return function (num2) {
    return num1 * num2;
  }
}

const multiplyByTwo = multiply(2);
console.log(multiplyByTow(4)); // 8

3. 부분 적용 (Partial Application)

function fetchData (baseUrl) {
  return function (endpoint) {
    return function (callback) {
      fetch(`${baseUrl}${endpoint}`)
      .then(callback);
    };
  };
}

const getFromAPI = fetchData('https://api.example.com');
const getUsers = getFromAPI('/users');
getUsers((data) => console.log(data));



실제 프로젝트에서의 활용 - 디바운스 구현

디바운스: 자주 발생하는 이벤트를 제어하여 성능을 최적화하는 기법

ex) 검색 시 API 호출을 줄이기위해 디바운스를 사용할 수 있음

// 디바운스 구현
function debounce(func, wait) {
  let timeout;
  
  return function executedFunction (...ars) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  }
}

// 사용 예
const handleSearch = debounce((event) => {
  // API 호출 등의 무거운 작업
  console.log('Searching: ', event.target.value);
}, 500); 

searchInput.addEventListener('input', handleSearch);



클로저와 메모리

클로저를 사용하여 이벤트 리스너를 설정하고, 해당 리스너를 제거하는 코드를 작성하여 메모리 해제에 신경써야 한다.

메모리 누수 가능성이 있는 코드

function heavyOperation () {
  const bigData = new Array(1000000);
  
  return function () {
    return bigData;
  }
}

// 메모리 누수 가능성
// bigData가 메모리에 계속 유지됨
const getData = heavyOperation();

메모리 관리를위한 이벤트 리스터 클린업 함수 제공

functino setupHandler() {
  const data = heavyData();
  
  element.addEventListener('click', function () {
    // data를 사용
  });
  
  // 클린업 함수 제공
  return function cleanup() {
    element.removeEventListener('click');
    // data 참조 제거
    data = null;
  }
}
profile
🌿

0개의 댓글