[JavaScript] 함수의 객체적 특성과 활용법

함수도 객체다

함수는 호출 가능한 객체(callable object)로서, 일반 객체처럼 프로퍼티를 가질 수 있습니다. 이를 통해 함수 자체에 데이터를 저장하거나 상태를 관리할 수 있습니다.

function sayHello() {
  console.log("안녕하세요!");
}

// 함수의 내장 프로퍼티 확인
console.log(sayHello.name);   // 출력: "sayHello"
console.log(sayHello.length); // 출력: 0 (매개변수의 수)

// 사용자 정의 프로퍼티 추가
sayHello.counter = 0;

// 함수 호출과 프로퍼티 활용
sayHello();                   // 출력: 안녕하세요!
sayHello.counter++;

console.log(`sayHello 함수는 총 ${sayHello.counter}번 호출되었습니다.`);
// 출력: sayHello 함수는 총 1번 호출되었습니다.

위 예제에서 sayHello 함수에 counter라는 사용자 정의 프로퍼티를 추가하여 호출 횟수를 추적하고 있습니다. 이는 함수가 객체이기 때문에 가능한 일이며, 함수의 동작과 상태를 결합하여 관리할 수 있습니다.

함수의 내장 프로퍼티: name과 length

name 프로퍼티

함수 선언문이나 함수 표현식에 의해 만들어진 함수는 자동으로 자신의 이름을 기억합니다. 익명 함수라도 할당된 변수의 이름을 통해 이름이 추론될 수 있습니다.

// 익명 함수 표현식
let greet = function() {
  console.log("안녕하세요!");
};

console.log(greet.name); // 출력: "greet"

length 프로퍼티

함수의 length 프로퍼티는 함수 선언 시 정의된 매개변수의 개수를 나타냅니다. 여기에는 나머지 매개변수(rest parameters)는 포함되지 않습니다.

function add(a, b, ...rest) {
  return a + b;
}

console.log(add.length); // 출력: 2

기명 함수 표현식

기명 함수 표현식은 함수 표현식에 이름을 부여하여, 함수 내부에서 자기 자신을 참조할 수 있게 합니다. 이는 재귀 호출  등에서 유용하게 사용됩니다.

let factorial = function computeFactorial(n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * computeFactorial(n - 1);
  }
};

console.log(factorial(5)); // 출력: 120

// 외부에서는 'computeFactorial' 이름으로 함수에 접근할 수 없습니다.
try {
  computeFactorial(5); // ReferenceError 발생
} catch (error) {
  console.error(error.message); // 출력: computeFactorial is not defined
}

위 예제에서 함수 표현식에 이름 computeFactorial을 부여하여 내부에서 자기 자신을 재귀적으로 호출합니다. 그러나 이 이름은 함수 내부에서만 유효하며, 외부 스코프에서는 접근할 수 없습니다. 이를 통해 외부 변수에 의존하지 않고 자기 자신을 참조할 수 있습니다.

함수 프로퍼티를 이용한 상태 관리

함수는 클로저(closure)를 사용하여 외부 변수를 기억할 수 있지만, 함수 자체에 프로퍼티를 추가하여 상태를 관리하는 방법도 있습니다.

function makeCounter() {
  function counter() {
    return counter.count++;
  }
  counter.count = 0; // 함수 프로퍼티로 초기값 설정
  return counter;
}

let counter1 = makeCounter();

console.log(counter1()); // 출력: 0
console.log(counter1()); // 출력: 1

// 함수 프로퍼티에 직접 접근하여 수정 가능
counter1.count = 10;
console.log(counter1()); // 출력: 10

이 방식은 외부에서 함수 프로퍼티에 직접 접근하여 값을 변경할 수 있으므로 잠재적인 위험이 있습니다.

클로저와의 비교

클로저를 사용하면 외부에서 변수에 직접 접근할 수 없으므로 데이터의 은닉성(encapsulation)을 보장할 수 있습니다.

function createCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter2 = createCounter();

console.log(counter2()); // 출력: 0
console.log(counter2()); // 출력: 1

// 외부에서 'count' 변수에 접근 불가
console.log(typeof count); // 출력: undefined

함수 객체적을 이용한 캐싱

function fibonacci(n) {
  // 캐시 초기화
  if (!fibonacci.cache) {
    fibonacci.cache = {};
  }
  // 캐시에 결과가 있으면 반환
  if (fibonacci.cache[n]) {
    return fibonacci.cache[n];
  }
  // 계산 및 캐싱
  if (n <= 1) {
    fibonacci.cache[n] = n;
  } else {
    fibonacci.cache[n] = fibonacci(n - 1) + fibonacci(n - 2);
  }
  return fibonacci.cache[n];
}

console.log(fibonacci(10)); // 출력: 55

위 예제에서 fibonacci 함수는 자신의 프로퍼티 cache를 사용하여 이미 계산된 값을 저장하고 필요할 때 재사용합니다.