자바스크립트 톺아보기10

이지영·2025년 1월 12일

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

자바스크립트에서 함수는 값으로 취급된다.
모든 값은 자료형을 가지고 있다 함수의 자료형은 객체이다.
함수는 호출 가능한 행동 객체라고 이해하면 쉽다. 우리는 함수를 호출할 수 있을 뿐만 아니라 객체처럼 함수에
프로퍼티를 추가 제거하거나 참조를 통해 전달할 수도 있다.

name 프로퍼티

함수 객체에는 몇 가지 쓸만한 프로퍼티가 있따.
name 프로퍼티를 사용하면 함수 이름을 가져올 수 있다.

fucntion sayHi(){
   alert('Hi);
}
alert(sayHi.name); // sayHi

함수 객체에 이름을 할당해주는 로직은 아주 똑똑해서 익명 함수라도 자동으로 이름이 할당된다

let sayHi = function () {
   alert("Hi")
};
alert(sayHi.name); sayHi

기본 값을 사용해 이름을 할당한 경우에도 마찬가지다.

function f(sayHi = function() {}){
   alert(sayHi.name); // sayHi
}

자바스크립트 명세서에 정의된 이 기능을 contextual name이라고 부른다.
이름이 없는 함수의 이름을 지정할 떈 컨텍스트에서 이름을 가져온다.

객체 메서드의 이름도 name 프로퍼티를 이용해서 가져올 수 있다.

let user = {
   sayHi() {
      // ...
   },
   sayBye: function() {
      //...
   }
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye

배열 안에서 함수를 생성하는 경우

let arr = [function() {}];
alert(arr[0].name); // 빈문자열
// 엔진이 이름을 설정할 수 없어서 name 프로퍼티의 값이 빈 문자열이 된다.

length프로퍼티

내장 프로퍼티 length는 함수 매개변수의 개수를 반환한다.

function f1 (a) {};
function f2 (a,b) {};
function many(a,b,...more) {}

alert(f1.length); //1
alert(f2.length); //2
alert(many.length); //2

위처럼 나머지 매개변수는 개수에 포함되지 않는다.
한편, lenth프로퍼티는 다른 함수 안에서 동작하난 함수의 타입을 검사할 때도 종종 사용된다.

기명 함수 표현식

기명 함수 표현식은 이름이 있는 함수 표현식을 나타내는 용어이다.
먼저 일반 함수 표현식을 살펴보자

let sayHi = function(who) {
   alert(`Hello, ${who}`);
}
여기에 이름을 붙여보자.
```jsx
let sayHi = function func(who) {
   alert(`Hello, ${who}`);
}

이름을 붙인다고 해서 뭐가 달라질까? 어떤 경우에 func라는 이름을 붙일까?

먼저 이렇게 이름을 붙여줘도 위 함수는 여전히 함수 표현식이다.

func라는 이름을 붙이더라도 여전히 표현식을 할당한 형태를 유지하기 때문에 함수 선언문으로 바뀌지 않는다.

이름을 추가한다고 해서 기존에 동작하던 기능이 동작하지 않는 일은 발생하지 않는다.

sayHi()로 호출하는 것도 여전히 가능하다.

대신 func와 같은 이름을 붙이면 두 가지 변화가 생긴다. 이 두 변화 때문에 기명 함수 표현식을 사용한다.
1. 일므을 사용해 함수 표현식 내부에서 자기 자신을 참조할 수 있다.
2. 기명 함수 표현식 외부에선 그 이름을 사용할 수 없다.

함수 sayHi()를 예시로 이에 대해 살펴보자.
함수 sayHi는 who에 값이 없는 경우, 인수 "Guest"를 받고 자기 자신을 호출한다.

let sayHi = function func(who) {
   if (who) {
      alert(`Hello, ${who}`);
   } else {
      func("Guest"); // func를 사용해서 자기 자신을 호출한다.
   }
}
sayHi(); // Hello, Guest

// 하지만 아래와 같이 func를 호출하는 건 불가능하다.
func(); // Error, func is not defined(기명 함수 표현식 밖에서는 그 이름에 접근할 수 없다.)

그런데 여기서 왜 중첩을 호출할 때 sayHi대신 func를 사용할까? <br>
대부분의 개발자는 아래와 같이 코드를 작성한다
```jsx
let sayHi = function(who) {
   if(who) {
      alert(`Hello, ${who}`);
   } else {
      sayHi("Guest");
   }
}

하지만 이렇게 코드를 작성하면 외부 코드에 의해 sayHi가 변경될 수 있다는 문제가 생긴다.
함수 표현식을 새로운 변수에 할당하고, 기존 변수에 null을 할당하면 에러가 발생한다.

let welcome = sayHi;
sayHi = null;

welcome(); // 중첩 sayHi호출은 더 이상 불가능!

에러는 함수가 sayHi르 ㄹ자신의 외부 렉시컬 환경에서 가지고 오기 때문에 발생한다.

지역 렉시컬 환경엔 sayHi가 없기 때문에 외부 렉시컬 환경에서 sayHi를 찾는데

함수 호출 시점에 외부 렉시컬 환경의 sayHi엔 null이 저장되어 있기 떄문에 에러가 발생한다.

함수 표현식에 이름을 붙여주면 이런 문제를 해결할 수 있다.

let sayHi = function func(who) {
   if(who) {
      alert(`Hello, ${who}`);
   } else {
      func("Guest"); // 원하는 값이 제대로 출력된다
   }
}
let welcome = sayHi;
sayHi = null;
welcome(); 

func라는 이름은 함수 지역수준에 존재하므로 외부 렉시컬 환경에서 찾지 않아도 된다. 외부 렉시컬 환경에서는 보이지도 않는다.
함수 표현식에 붙인 이름은 현재 함수만 참조하도록 명세서에 정의되어 있기 때문이다.

이렇게 기명 함수 표현식을 이용하면 sayHi나 welcome같은 외부 변수의 변경과 관계없이 func이라는 내부 함수 이름을 사용해
언제든 함수 표현식 내부에서 자기 자신을 호출할 수 있다.

함수 선언문엔 내부 이름을 지정할 수 없다.

지금까지 살펴본 내부 이름은 함수 표현식에서만 사용할 수 있고, 함수 선언문엔 사용할 수 없다. 함수 선언문엔 내부 이름을 지정할 수 있는 문법이 없다.
개발을 하다 보면 믿을만한 내부 이름이 필요할 떄가 생기곤 한다. 이 때 바로 함수 선언문을 기명 함수 표현식으로 다시 정의하면 된다.

summary

함수는 객체이다.
객체로서의 함수에서 사용할 수 있는 프로퍼티 두 가지

  • name: 함수의 이름이 저장, 대개는 함수 선언부에서 이름을 가져오는데, 선언부에 이름이 없는 경우엔 자바스크립트 엔진이 컨텍스트(할당 등)을 통해 이름을 추론한다.
  • length: 함수 선언부에 있는 인수의 수로 나머지 매개변수는 포함되지 않는다.

함수 표현식으로 함수를 정의하였는데 이름이 있다면 이를 기명 함수 표현식이라 부른다.
기명 함수 표현식의 이름은 재귀 호출과 같이 함수 내부에서 자기 자신을 호출하고자할 때 사용할 수 있다.

함수엔 다양한 프로퍼티를 추가할 수 있다. 널리 쓰이는 자바스크립트 라이브러리 상당수에서 이런 커스텀 프로퍼티를 잘 활용하고 있다.

이런 라이브러리들은 주요 함수 하나를 만들고 여기에 다양한 헬퍼 함수를 붙이는 식으로 구성된다.

이렇게 객체로서의 함수 특징을 이용해 커스텀 프로퍼티를 만들면 함수는 자기 자신을 이용해 원하는 일을 수행할 수 있고, 함수 자기 자신에 딸린 프로퍼티의 기능도 사용할 수 있다는 장점이 있다.

과제

숫자 설정과 감소가 가능한 counter 만들기

다음 makeCounter()코드를 수정해서 카운터가 감소하고 숫자를 설정하게 해보세요

  • counter()는 다음 숫자를 반환해야 합니다.
  • counter.set(value)는 counter를 value로 설정해야 합니다.
  • counter.decrease()는 counter를 1 감소시켜야 합니다.
function makeCounter() {
   let count = 0;
   function counter() {
      return count++
   }
   counter.set = value => count = value;
   counter.decrease = () => counte--;
   return counter;
}

0개의 댓글