[JS] 객체로서의 함수와 기명함수 표현식

학미새🐥·2023년 5월 7일
0

출처

자바스크립트에서 함수는 호출 가능한 행동 객체로, 객체의 일종이다.
따라서 함수에도 객체와 동일하게 프로퍼티 추가/제거 및 참조 전달이 가능하다.

함수 객체 내 property

'name' property

  • value : 함수의 이름
function sayHi() {
  console.log("Hi");
}

console.log(sayHi.name); // sayHi
  • contextual name : 이름이 없는 익명 함수의 경우, 기본값 등 컨텍스트에서 이름을 가져와 자동 할당한다.
  • 객체 메소드의 경우에도 키값에 따라 자동으로 이름이 잘 할당된다.
  • 하지만 익명함수에 적절히 할당할 이름이 없는 경우엔, 이름이 빈 문자열로 할당되기도 한다.
// 배열 안에서 함수를 생성함
let arr = [function() {}];
console.log( arr[0].name ); // <빈 문자열>

'length' property

  • value : 함수의 매개변수의 개수
  • 이때, 나머지매개변수는 개수에 포함되지 않는다.
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

console.log(f1.length); // 1
console.log(f2.length); // 2
console.log(many.length); // 2
  • length는 함수를 인수로 받는 함수에서 함수의 타입을 검사하여 선택하는 특별한 경우에도 활용될 수 있다.
    말이 어려운데, 예시를 통해서 살펴보자면,
function ask(question, ...handlers) {
  let isYes = confirm(question);

  for(let handler of handlers) {
    if (handler.length == 0) {
      if (isYes) handler();
    } else {
      handler(isYes);
    }
  }

}

// 사용자가 OK를 클릭한 경우, 핸들러 두 개를 모두 호출함
// 사용자가 Cancel을 클릭한 경우, 두 번째 핸들러만 호출함
ask("질문 있으신가요?", () => alert('OK를 선택하셨습니다.'), result => alert(result));

우선 코드 실행 과정부터 보자.
ask 함수의 인수로

  • "질문 있으신가요?" 문자열
  • () => alert('OK를 선택하셨습니다.')
  • result => alert(result)

를 전달한다.

첫번째 인수는 question 매개변수에 담기게 되고,
나머지 인수들은 handlers라는 함수를 요소로 가지는 배열에 담기게 된다.

question의 값으로 사용자에게 확인 창을 띄우고, 사용자의 응답을 isYes에 담는다.

js에서 confirm 메소드는 확인창을 띄우는 역할이다. 사용자가 OK 버튼을 누를 경우 true, 취소 버튼을 누를 경우 false를 반환한다.

이후 handlers 배열에 담긴, 전달받은 모든 함수를 순회하면서

  • 전달받은 함수의 매개변수가 0개일 때
    • 사용자가 OK 버튼을 눌렀다면 해당 함수를 실행한다.
  • 전달받은 함수의 매개변수가 있을 때
    • 사용자의 응답(true/false)를 인수로 담아 해당 함수를 실행한다.

이 코드 실행은 결국 이렇게 해석할 수 있다.

  • 사용자가 OK 버튼을 눌렀을 경우
    • handlers 배열에 있는 모든 함수가 실행되고,
  • 사용자가 취소 버튼을 눌렀을 경우
    • handlers 배열에 있는 함수들 중 매개변수가 있는 함수만 실행된다.

이렇게 length의 값, 즉 인수의 개수에 따라 함수를 다르게 실행할 수 있는 JS의 성질을 다형성 이라고 한다.

custom property

함수라는 객체에 프로퍼티를 추가하는 것도 가능하다.

function sayHi() {
  alert("Hi");

  // 함수를 몇 번 호출했는지 세봅시다.
  sayHi.counter++;
}
sayHi.counter = 0; // 초깃값

sayHi(); // Hi
sayHi(); // Hi

alert( `호출 횟수: ${sayHi.counter}` ); // 호출 횟수: 2회

여기서 주의해야할 점은
추가한 함수의 프로퍼티는 변수가 아니다!

sayHi.counter = 0 으로 프로퍼티를 추가한 것이
let counter = 0 과 같은 일을 한 것이 절대! 아니다.

커스텀 프로퍼티를 통해 클로저를 대체할 수도 있다.

function makeCounter() {

  // let count = 0 대신 아래 메서드(프로퍼티)를 사용함

  function counter() {
    return counter.count++;
  };

  counter.count = 0;

  return counter;
}

let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1

두 방법의 차이점은 count 값이 외부 변수에 저장되어있는 경우 드러납니다. 클로저를 사용한 경우엔 외부 코드에서 count에 접근할 수 없습니다. 오직 중첩함수 내에서만 count 값을 수정할 수 있습니다. 반면 함수 프로퍼티를 사용해 count를 함수에 바인딩시킨 경우엔 다음 예시와 같이 외부에서 값을 수정할 수 있습니다.

  • 클로저와 커스텀 프로퍼티 방식의 차이 :count 가 외부 변수값으로 저장되었을 때
    • 클로저는 외부 코드에서 count 접근이 불가, 중첩함수 내에서만 접근
    • 커스텀 프로퍼티의 경우 어디서든지 count 접근 가능

기명 함수 표현식

기명 함수 표현식 : 이름이 있는 함수 표현식

let sayHi = function(who) {
	console.log(Hello, ${who});
};

기존에 익숙했던 위의 형태에

let sayHi = function func(who) {
	console.log(Hello, ${who});
};

func 이라는 이름을 붙여주는 방식이다.

이러한 기명 함수 표현식을 활용하면 다음이 가능하다.

  1. 함수 표현식 내부에서 자기 자신을 참조할 수 있음
    (외부에선 이름 사용 불가)
 let sayHi = function func(who) {
  if (who) {
    console.log(`Hello, ${who}`);
  } else {
    func("Guest"); // func를 사용해서 자신을 호출합니다.
  }
};

func 이 아닌 sayHi 를 통해 자신을 호출하는 것도 물론 가능하지만
sayHi는 외부에서 null이 할당되는 등 변경될 가능성이 있기 때문에
에러 방지 차원에서 func라는 이름을 사용하는 것이 좋다.

다만, 이런 기명 함수 표현식은 말그대로 함수 표현식에서만 사용이 가능하고, 함수 선언문에서는 사용할 수 없다.
따라서 함수 선언문을 사용하다가도 내부 이름을 지정해줄 필요가 생길 경우에 함수 표현식으로 바꿔주면 된다!

profile
뭐든 다해보려는 공대생입니다

0개의 댓글