본문 바로가기

모던 자바스크립트

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

📢 자바스크립트에서 함수는 값으로 취급된다. 

 

◾️ 함수의 자료형은 객체이다. 

◾️ 함수는 호출이 가능한(callable) 행동 객체이다. 

◾️ 함수를 호출할 수도 있고, 객체처럼 함수에 프로퍼티를 추가, 제거하거나 참조를 통해 전달할 수도 있다. 

 

 

name 프로퍼티

◾️ name 프로퍼티를 사용하면 함수 이름을 가져올 수 있다. 

function sayHi() {
  console.log("Hi");
}

console.log(sayHi.name); //sayHi

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

let sayHi = function () {
  console.log("Hi");
}

console.log(sayHi.name); // sayHi

◾️ 기본값을 사용해 이름을 할당한 경우

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

f();

◾️ contextual name : 이름이 없는 함수의 이름을 지정할 땐 컨텍스트에서 이름을 가져온다. 

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

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

console.log(user.sayHi.name); // sayHi
console.log(user.sayBye.name); // sayBye

◾️ 객체 메서드 이름은 함수처럼 자동 할당이 되지 않는다. (이름 추론이 불가능 할 때 name 프로퍼티엔 빈 문자열이 저장된다.)

let arr = [function () {}];

console.log( arr[0].name ); // <빈 문자열>

 

length 프로퍼티

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

◾️ 나머지 매개변수는 개수에 포함되지 않는다. 

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

◾️ 다른 함수 안에서 동작하는 함수의 타입을 검사할 때도 사용된다. 

◾️ 질문에 쓰일 question과 질문에 대한 답에 따라 호출할 임의의 수의 handler 함수를 함께 받는 함수 ask

◾️ 사용자가 답을 제출하면 ask는 handler함수를 호출한다. 이때 두 종류의 handler 함수를 ask에 전달할 수 있다. 

  • 인수가 없는 함수로, 사용자가 OK를 클릭했을 때만 호출됨
  • 인수가 있는 함수로, 사용자가 OK를 클릭하든 Cancel을 클릭하든 호출됨

◾️ handler.length 프로퍼티를 사용하면 상황에 맞는 handler를 호출할 수 있다. 

◾️ 사용자가 긍정적인 대답을 했을 때 사용 할 인수가 없는 핸들러를 하나 만들고, 사용자의 응답 종류와 관계없이 범용적으로 사용할만한 핸들러도 구축해 ask 내부에서 handler.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));

◾️ 인수의 종류에 따라(또는 인수의 length 프로퍼티 값에 따라) 인수를 다르게 처리하는 방식을 프로그래밍 언어에선 다형성(polymorphism)이라고 부른다. 

 

 

커스텀 프로퍼티

◾️ 함수에 자체적으로 만든 프로퍼티를 추가할 수도 있다. 

function sayHi() {
  console.log("Hi");
  
  // 함수를 몇 번 호출했는지 카운트
  sayHi.counter++;
}

sayHi.counter = 0; // 초깃값

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

console.log(sayHi.counter); // 2
⚠️ 프로퍼티는 변수가 아니다. 

◾️ sayHi.counter = 0과 같이 함수에 프로퍼티를 할당해도 함수 내에 지역변수 counter가 만들어지지 않는다. 
   ( counter 프로퍼티와 변수 let counter는 전혀 관계가 없다. )
◾️ 프로퍼티를 저장하는 것처럼 함수를 객체처럼 다룰 수는 있지만, 실행에 아무 영향을 끼치지 않는다. 
  ( 변수는 함수 프로퍼티가 아니고 함수 프로퍼티는 변수가 아니다)

◾️ 클로저는 함수 프로퍼티로 대체할 수 있다. 

function makeCounter() {
  // let count = 0 대신 프로퍼티를 사용함
  
  function counter() {
    return counter.count++;
  }
  
  counter.count = 0;
  
  return counter;
}

let counter = makeCounter();
console.log( counter() ); // 0
console.log( counter() ); // 1

◾️ 이제 count는 외부 렉시컬 환경이 아닌 함수 프로퍼티에 바로 저장된다. 

 

◾️ 두 방법의 차이점은 count 값이 외부 변수에 저장되어 있는 경우 드러난다.

◾️ 클로저를 사용한 경우엔 외부 코드에서 count에 접근할 수 없다. (중첩함수 내에서만 count값을 수정할 수 있다)

◾️ 함수 프로퍼티를 사용해 count를 함수에 바인딩시킨 경우엔 외부에서 값을 수정할 수 있다. 

function makeCounter() {
  
  function counter() {
    return counter.count++;
  }
 
  counter.count = 0;
  
  return counter;
}

let counter = makeCounter();

counter.count = 10;
console.log( counter() ); // 10

◾️ 구현 방법은 목적에 따라 선택하자. 

 

 

기명 함수 표현식

◾️ 기명 함수 표현식(Named Function Expression, NFE)이름이 있는 함수 표현식을 나타내는 용어

◾️ 일반 함수 표현식

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

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

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

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

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

sayHi("suzu"); // Hello, suzu

◾️ 이름을 붙이면 생기는 두 가지 변화

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

sayHi(); // Hello, Guest

// 기명 함수 표현식 외부에선 그 이름을 사용할 수 없다. 
func(); // Error, func is not defined

 

◾️ 중첩 호출을 할 때 sayHi를 사용하여 코드를 작성하면 외부 코드에 의해 sayHi가 변경될 수 있다는 문제가 생긴다. 

◾️ 함수 표현식을 새로운 변수에 할당하고, 기존 변수에 null을 할당하면 에러가 발생한다. 

let sayHi = function (who) {
  if(who){
    console.log(`Hello, ${who}`);
  } else {
    sayHi("Guest"); // TypeError: sayHi is not a function
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // sayHi는 더이상 호출이 불가능하다. 

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

◾️ 지역(local) 렉시컬 환경엔 sayHi가 없기 때문에 외부 렉시컬 환경에서 sayHi를 찾는데, 함수 호출 시점에 외부 렉시컬 환경의 sayHi엔 null이 저장되어 있기 때문에 에러가 발생한다. 

 

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

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

◾️ 내부 이름은 함수 표현식에만 사용할 수 있다. 
◾️ 내부 이름이 필요할 때가 생길 땐 함수 선언문을 기명 함수 표현식으로 다시 정의하면 된다. 

 

📝 요약

◽️ 함수는 객체이다. 
◽️ 객체로서의 함수에서 사용 할 수 있는 두 가지 프로퍼티
     1. name : 함수의 이름이 저장된다. 보통 함수 선언부에서 이름을 가져오는데, 선언부에 이름이 없는 경우엔 자바스크립트           엔진이 컨텍스트(할당 등)를 이용해 이름을 추론한다. 
     2. length: 함수 선언부에 있는 인수의 수로 나머지 매개변수는 포함되지 않는다. 

◽️ 함수 표현식으로 함수를 정의하였는데 이름이 있다면 이를 기명 함수 표현식이라고 한다. 
◽️ 기명 함수 표현식의 이름은 재귀 호출과 같이 함수 내부에서 자기 자신을 호출하고자 할 때 사용할 수 있다. 
◽️ 함수에는 다양한 프로퍼티를 추가할 수 있다. (주로 라이브러리에서 활용 됨)
◽️ 객체로서의 함수 특징을 이용해 커스텀 프로퍼티를 만들면 함수는 자기 자신을 이용해 원하는 일을 수행할 수 있고, 함수 자  기 자신에 딸린 프로퍼티의 기능도 사용할 수 있다는 장점이 있다. 

 



📝 오늘 새롭게 배운 내용 📝

 

✅ 함수의 자료형은 객체다!!!

✅ 함수의 이름을 알고 싶을 땐 name 프로퍼티를 사용한다. 

 함수의 매개변수가 몇개인지 알고 싶을 땐 length 프로퍼티를 사용한다. 

(객체나 배열에선 요소의 개수를 가져올 때 사용하는데, 함수에선 인수의 개수!!! )

 커스텀 프로퍼티를 만들어 사용할 수도 있다. 

✅ 생긴건 변수와 비슷해 보이지만 프로퍼티는 변수가 아니다. 

✅ 함수내에 변수를 사용하면 외부에서 접근할 수 없지만 프로퍼티를 사용하면 접근할 수 있다.

✅ 함수 표현식에서도 함수에 이름을 붙일 수가 있다.

(let 변수 = 함수 + '함수이름' () {} )

변수에 할당는게 함수에 이름을 붙이는거라 생각했는데

변수에 함수를 할당하면 (함수도 값이기 때문에) 외부 코드에 의해 변경될 가능성이 있다.

함수에 이름을 붙이면 변경될 가능성이 없기 때문에 에러가 발생하지 않는다. 

✅ 그래서 함수 내부에서 자기 자신을 부를 수가 있다. (재귀호출)

재귀 호출을 할 때 변수를 호출하기 보단 함수에 이름을 붙여서 호출하는게 나을거 같다. 

✅ 그런데!! 내부 이름은 함수 표현식에만 사용할 수 있다. 

 

 

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

 

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

 

ko.javascript.info

 

 

 

 

'모던 자바스크립트' 카테고리의 다른 글

6.8 setTimeout과 setInterval을 이용한 호출 스케줄링  (0) 2020.08.26
6.7 new Function 문법  (0) 2020.08.25
6.5 전역 객체  (0) 2020.08.22
6.4 오래된 'var'  (0) 2020.08.21
6.3 변수의 스코프  (0) 2020.08.20