03. Functions
함수는 자바스크립트 Application의 기본적인 구성요소로
추상화 계층을 구축하고, 클래스를 모방하거나 정보 은닉, 모듈에 대한 방법을 제공함
타입스크립트에서는 함수의 작업을 더 수월하게 하기 위해 몇 가지 추가적인 기능을 제공함
자바스크립트와 마찬가지로 함수를 익명/기명으로 생성할 수 있음
// 기명 함수(named function)
function add(x, y) {
return x + y;
}
// 익명 함수(anonymous function)
let myAdd = function(x, y) { return x + y; };
자바스크립트 함수에서 함수 바깥의 변수에 접근할 수 있고 이를 캡쳐 한다고 표현함
이 동작 방식을 알고 있는 것이 앞으로의 내용을 이해하는데 중요
let z = 100;
function addToZ(x, y) {
return x + y + z;
}
함수 타입(Function Types)
함수에 타입을 넣기(Typing the function)
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number {
return x + y;
};
위에서 생성한 자바스크립트 함수의 기본적인 형태에 파라미터, 리턴 값에 타입을 추가할 수 있음
return
문을 보고 타입을 추론할 수 있기 때문에 리턴 값의 타입은 생략할 수 있음
개인적으로 써주는게 좋다고 생각.. 시그니처만 보고 알 수 있는게 코드 이해하기가 수월함
함수타입 작성하기(Writing the function type)
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
함수의 타입에는
- 매개변수 타입
- 반환 타입
이 있어서 전체 함수 타입을 작성하고자 한다면 이 두 가지가 필요함
함수 타입에 붙는 이름은 함수 내에서 사용하는게 아니라 가독성을 위한 것이라 아래와 같이 쓸 수도 있음
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
baseValue
,increment
는 타입을 위해서 쓰는 변수이고실제로 함수 내에서 쓰는 변수는
function
에 있는x
,y
- 지정된 타입만 비교하고 이름은 안보기 때문에 이렇게 서로 다르게 해도 상관없음
(하지만 변수명 짓기도 어려운데 굳이 다르게 할 일은 잘 없겠다 싶은 생각..ㅎ)
- 함수의 타입에는 위에서 말했듯이 매개변수와 반환 타입이 필요하기 때문에
=>
뒤에 반드시 반환타입이 명시되어야 함만약 함수가 반환하는게 없다면
void
를 명시해주어야 함C/C++ 배우고 온 우리한테는 자연스러운 부분이지만 다른 언어 안써본 사람들에게는 낯설수도 있겠다..
- 함수 내부에서 사용하는 함수 외부의 captured 변수는 타입에 영향을 안 줌
실제로 captured 변수는 함수의 "숨겨진 상태(hidden state)"로 API를 구성하지 않음
함수형 프로그래밍과 순수 함수 내용이 생각나는 지점.. side effect..
타입 추론(Inferring the types)
한쪽에만 타입이 있어도 컴파일러가 알아서 타입을 추론함
솔직히 이건 필수적인 부분이라고 생각함; 위에 두 번 쓰는거 보고 DRY하지 못함에 분노..
// myAdd는 전체 함수 타입을 가짐
let myAdd = function(x: number, y: number): number { return x + y; };
// 매개변수 x 와 y는 number 타입을 가짐
let myAdd: (baseValue: number, increment: number) => number =
function(x, y) { return x + y; };
이런 타입 추론 방식을 "contextual typing" 라고 부름
이를 통해서 프로그램에서 타입을 유지하기 위한 수고를 덜 수 있음 (아니었음 타입스크립트 때려침;)
선택적/기본 파라미터(Optional and Default Parameters)
타입스크립트에서는 모든 파라미터가 함수에 필요하다고 가정함
파라미터에 null
이나 undefined
를 줄 수 없다는 것이 아니라,
컴파일 단계에서 해당 함수를 호출하는 지점이 파라미터에 명시된 타입의 값을 넘겼는지 확인한다는 것
만약 null
이나 undefined
를 파라미터에 지정했다면 그렇게 넘겼는지를 봄

선택적 파라미터
자바스크립트에서처럼 선택적으로 파라미터를 넘기고 싶다면 (필요할 땐 넘기고 아니면 안넘김 = undefined
)
매개변수의 이름 끝에 ?
를 붙여서 해당 파라미터를 선택적으로 만들 수 있음

- 위의 코드에서
lastName
을 선택적 파라미터로 변경함으로써파라미터 갯수를 적게 넘긴 첫 번째 실행은 성공하지만
여전히 정해진 파라미터 수보다 많이 넘긴 경우에는 오류가 됨
= 선택적 파라미터를 쓰더라도 반드시 파라미터가 함수에 정의되어 있어야 함
- 주의 : 선택적 파라미터는 파라미터 순서에서 제일 뒤에 와야 함
기본 파라미터
값을 제공하지 않았거나 undefined
를 받았을 때 할당할 값을 지정 가능
자바스크립트에서 파라미터 기본 값을 지정하는거랑 똑같음

기본 파라미터를 지정한 경우 result1
을 보면 선택적 파라미터처럼
파라미터를 넘기지 않아도 되는 것을 알 수 있음
= 선택적 파라미터와 동일하게 firstName: string, lastName?: string => string
타입을 가짐

선택적 파라미터와 다른 점은 꼭 맨 뒤에 위치하지 않아도 됨
Rest 파라미터
interface somtt {
[asdfasdf: string]: boolean | string | ..;;
}
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
...
으로 rest 파라미터를 추가할 수 있고, 그 타입은 배열형의 타입을 지정하듯이 하면 됨
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
함수 타입을 만들 때도 똑같이 사용할 수 있음
this
this와 화살표 함수
자바스크립트에서 this
는 함수가 호출될 때 설정됨
이런 바인딩이 강력하고 유연하지만 함수가 호출되는 context를 항상 잘 알아야하는 어려움이 있음
특히 함수를 파라미터로 넘기거나 반환하는 경우에 혼란을 유발할 수 있음
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {
suit: this.suits[pickedSuit],
card: pickedCard % 13
};
}
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
- 위 예제를 실행하면 결과 출력 대신에 오류가 발생할 것
cardPicker
의 실행에서this
는 전역 객체를 가리키고 있기 때문에this.suits
는undefined
가 됨
- 짚고 넘어갈 것 : 여기에서
this
는 any 타입을 가짐
- 위에서
createCardPicker
의 반환하는 함수를 화살표 함수로 만들어서this
가 의도한대로 deck 객체를 가리키도록 할 수 있음
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
타입스크립트에서 --noImplicitThis
옵션을 컴파일러에 전달하면 위 경우 경고를 띄움

this 파라미터
위의 예제에서 this
의 타입은 여전히 any
임 (deck를 가리키니까 타입도 deck였으면 좋겠는데 아님)
이유는 객체 리터럴 내부에 있는 함수에서 시작된 것이기 때문
→ 이 문제를 해결하기 위해서 명시적으로 this
파라미터를 전달할 수 있음
function f(this: void) {
// 독립형 함수에서 `this`를 사용할 수 없는 것을 확인함
}
- this parameters are fake parameters that come first in the parameter list of a function:
this
파라미터는 함수의 파라미터 목록 제일 처음에 오는 가짜 파라미터임 (?)
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// callee가 Deck 타입이어야 함을 명시함
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {
suit: this.suits[pickedSuit],
card: pickedCard % 13
};
}
}
};
- 이제 컴파일러는 함수 내에서 참조하고 있는
this
가 Deck 타입이라는 것을 알기 때문에--noImplicitThis
으로 실행해도 오류가 발생하지 않음
callback 함수에서의 this 파라미터
사용하는 라이브러리에서 나중에 호출할 수 있도록 콜백함수를 전달할 때도
this
로 인한 오류가 발생할 수 있음
→ 라이브러리에서 콜백함수를 일반 함수처럼 호출해서 this
가 undefined
가 될 수 있음
interface UIElement {
addClickEventListener(onclick: (this: void, e: Event) => void): void;
}
this: void
의 의미 : addClickEventListener는 함수 타입인onclick
을 파라미터로 받는데,이
onclick
함수에서는this
를 사용하지 않음
/* 나쁜 예제 */
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// 여기에서 this를 사용했으니 runtime 호출 시에 오류가 날 것
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad);
/* 좋은 예제 */
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// this는 void 타입이기 때문에 이 함수 내부에서 this를 이렇게 안써야 함
console.log("clicked!");
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
this
를 쓰고 싶다면 화살표 함수를 이용해야 함
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
- 화살표 함수는 outer this를 사용하기 때문에,
this: void
를 기대하는 곳에 해당 함수를 전달할 수 있음
- 화살표 함수 사용의 단점은 핸들러 타입의 각 객체마다 함수를 각각 만든다는 것 (함수 여러개 생성)
메서드는 한 번만 생성되고, Handler 객체의
prototype
에 저장됨 (Handler 객체간 공유)ref: 화살표 함수를 남용하면 안되는 이유
- 자신이 호출되면서 생성된 실행 콘텍스트에서
thisBinding
정보를 생성하지 않음
this
가 위치한 스코프에서this
가 무엇인지 찾고, 해당 스코프에this
가 없다면 스코프 체인을 타고 올라가며 가장 가까운this
를 참조함
- 화살표 함수는
prototype
이 존재하지 않음→ 화살표 함수로 선언된 함수를 new와 함께 호출하더라도,
prototype
객체가 없기 때문에 자신의 인스턴스 객체가 만들어질 수 없음→ 생성자로 사용할 수 없음
arguments
프로퍼티를 생성하지 않음→ 화살표 함수 내에서
arguments
를 참조하면 ReferenceError 발생하거나 상위 실행 콘텍스트가 있다면 스코프상의arguments
객체를 참조
- 자신이 호출되면서 생성된 실행 콘텍스트에서
Overload
자바스크립트는 선천적으로 매우 동적인 언어로,
하나의 자바스크립트 함수가 파라미터에 따라서 다른 타입의 객체를 반환하는 것은 흔한 일임
자바스크립트에는 오버로드가 없다
- JavaScript function definitions do not specify data types for parameters.
- JavaScript functions do not perform type checking on the passed arguments.
- JavaScript functions do not check the number of arguments received.
ref. https://kevinkreuzer.medium.com/typescript-method-overloading-c256dd63245a
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: any): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard; // number
}
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 }; // object
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
파라미터로 넘긴 값에 따라서 pickCard
가 리턴하는 값의 타입이 달라짐
- 숫자를 넘기면 : 해당하는 카드의 정보를 알려줌
- 카드를 넘기면 : 해당 카드의 숫자를 알려줌
이것을 어떻게 타입 시스템으로 표현할 것인가..
→ 같은 이름의 함수를 여러 개 제공하면 됨 (오버로드)
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
- 오버로드 목록에서 순차적으로 맞는 오버로드 함수를 찾고, 찾은 경우 해당 함수를 호출하므로
가장 구체적인 케이스를 먼저 두어야 함
- 위 예제에서 any 는 오버로드 목록에 들어가지 않음 → 오버로드 함수는 총 2개
- 객체와 숫자 외에 다른 방식으로
pickCard
호출을 시도하면 오류가 발생함