📢 자바스크립트에서 함수는 값으로 취급된다.
◾️ 함수의 자료형은 객체이다.
◾️ 함수는 호출이 가능한(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
◾️ 이름을 붙이면 생기는 두 가지 변화
- 이름을 사용해 함수 표현식 내부에서 자기 자신을 참조할 수 있다.
- 기명 함수 표현식 외부에선 그 이름을 사용할 수 없다.
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 객체로서의 함수와 기명 함수 표현식]
'모던 자바스크립트' 카테고리의 다른 글
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 |