🖥️

5. 함수와 클래스

 

5.1 함수의 선언과 실행

 

5.1.1 함수에 타입을 선언하는 방법

3. 자바스크립트가 가지지 못한 녀석, 타입에서 함수에 타입을 선언하는 방법에 대해서 간략하게 다뤄봤다. 이번 파트에서 타입스크립트로 함수를 선언하고 실행하는 방법에 대해 자세히 알아보자. 함수는 매개변수 명 옆에 타입을 선언할 수 있고, 리턴값에 타입을 선언할 수 있다. 리턴 타입을 기입하지 않고, 타입을 추론하게 할 수도 있다. 함수를 실행할 때는 인자에 따로 타입을 선언하지 않아도 된다. 왜냐하면, 함수의 매개변수와 인자의 타입이 호환되는지 확인하기 때문이다.
 
  • 함수 선언문
// 리턴 타입이 추론된 방식 function food1(name:string){ return 'good' + name } food1('chicken') //'goodchicken' // 리턴 타입이 선언된 방식 function food1(name:string):string{ return 'good' + name } food1('chicken') //'goodchicken'
 
  • 함수 표현식
//리턴 타입이 추론된 방식 let food2 = function(name:string){ return 'good' + name } food2('chicken') //'goodchicken' //리턴 타입이 선언된 방식 let food2 = function(name:string):string{ return 'good' + name } food2('chicken') //'goodchicken'
 
  • 화살표 함수
//리턴 타입이 추론된 방식 let food3 = (name:string) =>{ return 'good' + name } food3('chicken')//'goodchicken' //리턴 타입이 선언된 방식 let food3 = (name:string):string =>{ return 'good' + name } food3('chicken')//'goodchicken'
 
위 예제들은 모두 리턴 값이 있다. 리턴값을 반환하고 싶지 않을 때는 void 타입을 사용할 수 있다. 3장 void 타입에서 자세히 설명되어있다.
function food(price:number):void{ console.log(price); } food(20000); //20000;
 
 

5.1.2 선택적 매개변수

자바스크립트에서는 매개변수를 입력했지만, 인자가 없다고 해서 에러가 나지 않는다. 아래의 콘솔창에서 보여지는 예시와 같이 매개변수와 인자의 개수를 다르게 입력했지만 에러가 나지 않고 undefined로 나온다.
// JavaScript function food(price,name){ console.log(price,name) } food(10000); //10000,undefined
notion imagenotion image
 
하지만, 타입스크립트에서는 매개변수에 타입을 선언했을 경우, 인자를 필수로 입력해야한다. 아래 예제를 보면 price와 name 매개변수 2개를 생성했다. 함수를 실행 할 때 매개변수와 인자의 개수가 일치하지 않으면 아래와 같이 에러가 발생하는 것을 볼 수 있다.
//TypeScript function food(price:number,name:string):void{ console.log(price,name) } food(10000);
notion imagenotion image
notion imagenotion image
 
인자의 개수를 파라미터의 개수와 통일해서 에러를 해결할 수 있다. 더불어, 매개변수가 선택사항이라는 표시를 해주는 ?(물음표)를 매개변수 이름 뒤에 붙여도 에러를 해결할 수 있다. 이를 선택적 매개변수(optional parameter)라고 한다. 즉, 선택적 매개변수로 지정한 경우에는 인자를 생략할 수 있다.
function food(price:number,name?:string):void{ console.log(price,name) } food(10000); //10000,undefined
 
선택적 매개변수는 undefined를 활용한 유니온 타입으로도 나타낼수 있다.
function food(price:number,name?:string|undefined):void{ console.log(price,name) } food(10000); //10000,undefined
 
 

5.2 타입스크립트의 함수 표현

 

5.2.1 Call signature

위에 함수를 선언하는 3가지의 방식(함수 선언문, 함수 표현식, 화살표 함수)을 알아보았다. 지금까지 함수를 선언할 때 일일이 타입을 지정해 주었지만 함수 또한 미리 타입을 지정해 줄 수 있다. 함수 타입을 미리 선언해 주는 문법을 호출 시그니처(Call signature)라고 한다.
 
다음과 같이 함수 타입을 미리 지정해 줄 수 있다.
 
  • 변수를 이용해 정의
// 변수에 함수 타입을 지정 let Addnum: (a:number, b:number) => number; Addnum = function(a,b) { return a + b; }
 
  • type을 이용해 정의
// type으로 함수 타입을 지정 type Addnum = (a:number, b:number) => number; const addnum: Addnum = (a, b) => a + b;
 
  • 인터페이스를 이용해 정의
// 인터페이스로 함수 타입을 지정 interface Name { (firstName: string, lastName: string): string; } const Myname: Name = function(first, last) { return first + last; }
 
타입을 미리 지정해주고 난 뒤 함수에 마우스를 올려보면 아래와 같이 타입이 지정되어 있는 것을 볼 수 있다.
notion imagenotion image
 
 

5.2.2 this

자바스크립트에서 this키워드는 호출 방식에 따라 값이 동적으로 변한다. this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 참조 변수이다. 아래 표를 통해 함수 호출 방식에 따라 this 가 어떤 값을 가리키는지 살펴보자.
호출 방식
일반 함수
메서드
생성자 함수
화살표 함수
가리키는 값
전역객체
메서드를 호출한 객체
생성자 함수가 생성할 인스턴스
상위 스코프
이렇게 자바스크립트에서 this의 값은 여러가지로 호출되기 때문에 참조 시 오류가 나기 쉽다. 이 경우 타입스크립트에서 this의 타입을 명시해 오류를 막을 수 있다.
 
 

5.2.2.1 명시적 this

첫 번째 매개변수로 this의 타입을 선언하여 사용한다.
function lion(this: type, a) { // ...code } lion(a) // 실제 매개변수는 this 다음으로 오는 매개변수.
this 타입을 명시적으로 적어 this의 타입이 가리키는 것을 지정해 줄 수 있다. 매개변수 자리에 넣어주지만 사실 가짜 매개변수로 실제 인자값을 받지는 않는다. 실제 인자값을 받는 매개변수는 this 다음으로 오는 매개변수들이다.
 
  • 예시1
// this의 타입을 Fruits으로 지정 interface Fruits { name: string, price: number, purchase(this: Fruits): () => {}; } let mart: Fruits = { name: 'banana', price: 3000, purchase: function(this: Fruits) { return () => { return this.name; } } } let getFruit = mart.purchase(); let fruitName = getFruit(); console.log(fruitName); // banana
 
  • 예시2
// this의 타입을 Lion으로 지정, 첫번째 this는 가짜 매개변수 interface Lion { name: string } const lion: Lion = { name: 'licat' } function animal(this: Lion, a: string) { console.log( a + this.name ) } animal.call(lion, 'Hello'); // HelloLicat
call, bind, apply 함수는 this를 지정 객체에 묶을 수 있다
 

5.2.2.2 콜백함수에서 this

 
  • 예시1
콜백 함수를 전달할 때 this 때문에 오류가 날 수 있다. 이때 this를 구분하여 콜백 오류를 막을 수 있다. [1]
interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; } // 위 함수의 this: void는 addClickListener가 onclick이 this타입을 요구하지 않는 함수라는 것을 의미한다. 즉 onclick에서 this는 사용 불가하다. class Handler { info!: string; onClickBad(this: Handler, e: Event) { // 위의 UIElement 인터페이스에서 'this'가 필요없다고 했지만 사용했기 때문에 에러가 발생한다. this.info = e.message; } } let h = new Handler(); uiElement.addClickListener(h.onClickBad); // error
 
TypeScript는 addClickListener가 this: void를 갖는 함수를 필요로 한다는 것을 감지한다. 오류를 고치기 위해 this의 타입을 바꿔준다
class Handler { info!: string; onClickGood(this: void, e: Event) { // void 타입 때문에 this는 이곳에서 쓸 수 없다 console.log('clicked!'); } } let h = new Handler(); uiElement.addClickListener(h.onClickGood); // ok
 
  • 예시2
const obj = { a: 'Hi~', b: function(){ console.log(this.a); } }; obj.b(); // Hi~ const lion = obj.b; lion(); // Cannot read property 'a' of undefined function animal (cb: any) { cb(); } animal(obj.b); // Cannot read property 'a' of undefined
 
호출하지 않는 메서드를 할당하는 경우 this의 콘텍스트를 잃어 a를 참조할 수 없게된다. 이 경우를 해결하기 위해 bind 메서드를 사용해 this를 직접 연결할 수 있다.
const b = obj.b.bind(obj); b(); // Hi~ function animal(cb: any) { cb(); } animal(obj.b.bind(obj)); // Hi~
 

5.2.2.3 화살표 함수의 this

화살표 함수 내에서 this는 상위 스코프의 this 를 가리킨다.
 
  • 예시1
const obj = { a: 'cat~', b: 'dog~', c: function () { console.log(this.a); }, d: function (){ // 화살표 함수의 this는 메서드 d 함수 내의 this 스코프를 참조 return () => console.log(this); }, e: function (){ return () => console.log(this.b); }, f: () => console.log(this); // error //메서드 e의 this는 전역객체를 참조하기 때문에 에러가 나온다. }; obj.c(); // cat~ obj.d()(); // { a: 'cat~', b: 'dog~', c: [Function: c], d: [Function: d] } obj.e()(); // dog~ const c = () => obj.c(); const d = () => obj.d(); const e = () => obj.e(); c(); // cat~ d()(); // { a: 'cat~', b: 'dog~', c: [Function: c], d: [Function: d] } e()(); // dog~ function animal(cb: any) { cb(); } animal(() => obj.c()); // cat~ animal(() => obj.d()()); // { a: 'cat~', b: 'dog~', c: [Function: c], d: [Function: d] } animal(() => obj.e()); // dog~
 
  • 예시2
class 내부의 this는 생성할 인스턴스를 가리킨다.
class Dog { constructor(private name: string) {} dogName = () => { console.log(this.name); } } const dog = new Dog('happy'); dog.dogName(); // happy const dogName = dog.dogName; dogName(); // happy function animal(cb: any) { cb(); } animal(dog.dogName); // happy
 
 

5.2.3 Overloading

타입스크립트의 함수 오버로딩(Overloading)은 함수 이름은 같지만 매개변수 또는 반환 타입이 다른 여러 함수를 가질 수 있는 것을 말한다. 자바스크립트의 함수는 오버로딩이라는 개념이 적용되지 않는데 애초에 전달받는 인자값과 반환 타입에 제약이 없어 자유롭기 때문이다. 하지만 타입스크립트의 함수는 인자 타입과 반환 타입을 지정해주어야 하기 때문에 오버로딩을 사용할 수 있다.
 
타입스크립트의 오버로딩은 선언부와 구현부로 나뉜다. 선언부에서는 매개 변수의 타입과 반환 타입만 지정해주고 구현부에서는 실제 함수 구현이 이루어진다. 함수명은 동일 해야하고 오버로딩을 통해 다양한 함수 타입을 정의할 수 있지만 실제 구현은 한 번만 가능하기 때문에 각 케이스 구분은 구현부 내에서 이루어져야 한다.
 
  • 예시1
function likelion(a: string, b: string): string; // 선언부 function likelion(a: number, b: number): number; // 선언부 function likelion(a: any, b: any): any { // 구현부 return a + b; } likelion('licat ', 'gary~'); // licat gary~ likelion(10, 20); // 30 likelion('hello ', 2); // Error
 
타입스크립트에서 any 타입은 지양하지만 오버로드 함수에 쓰는 이유
 
오버로드 함수 실행부에서 인수와 리턴 타입을 any로 적어주었다. 컴파일러는 오버로드 함수 선언부의 타입들만 보고 함수를 판단하기에 구현부에 any를 써도 상관없다. 오히려 따로 타입을 지정 안해도 되기 때문에 구현부에는 any타입을 많이 사용한다.
 
반환타입이 맞지 않은 경우 다음과 같은 에러 메세지를 볼 수 있다.
 
notion imagenotion image
 
타입 별칭이나 인터페이스의 메서드 정의에서도 오버로딩 사용이 가능하다.
 
  • 예시2
type User = { (a: number, b: number) : number (a: number, b: string) : number } const user: User = (a,b) => { if(typeof b === 'string') return a return a + b } user(10,'lion') // 10 user(10,20) // 30
 
  • 예시3
interface User { name: string, age: number, getInformation(x: string): string[]; getInformation(y: number): string; } let user: User = { name: 'Harry', age: 30, getInformation: (a: any) => { if (typeof a === 'string') { return a.split(''); } else { return a.toString(); } } }; user.getInformation('Poter') // [ 'P', 'o', 't', 'e', 'r' ] user.getInformation(30) // '30'
함수의 인자 숫자와, 반환 타입이 같은경우 오버로딩을 사용하지 않아도 된다. 간단하게 유니온 타입으로 작성하는 것이 가독성에 더 좋다.
 
  • 예시4
function checklen(a: string): number; // 선언부 function checklen(arr: any[]): number; // 선언부 function checklen(x: any): number { // 구현부 return x.length; } checklen("123"); // 3 checklen([123]); // 1 checklen(['123']); // 1 // 위와 같은 코드다 function checklen(a: any[] | string) { return a.length; } checklen("123"); // 3 checklen([123]); // 1 checklen(['123']); // 1
 
공식문서에 따르면 가능한 오버로딩보다 유니온 타입을 쓰는 것을 권한다.
notion imagenotion image
 
 

5.3 클래스

 

5.3.1 접근제한자

자바스크립트에서는 클래스의 프로퍼티들을 은닉화 할 수 있는 마땅한 방법이 없다. 이러한 문제점을 해결하기 위해 타입스크립트의 접근제한자를 사용하게 되었다. 접근 제한자는 생성된 객체의 프로퍼티와 메서드들의 접근 권한를 지정할 수 있으며, 3가지의 접근 제한자를 지원한다.
 

5.3.1.1 접근제한자의 종류

  • public 클래스 내부, 상속 받은 자식(서브) 클래스, 객체 모두 접근이 가능하게 해주는 접근제한자이다. 만약 생략하게 된다면 기본값으로 public이 설정된다.
  • protected 해당 클래스를 포함한 자식(서브) 클래스에서만 접근이 가능하게 해주는 접근제한자이다.
  • private 선언된 클래스 내부에서만 접근이 가능하고 외부에서는 접근이 불가능하게 하는 접근제한자이다. class내에서만 접근이 가능하기 때문에 의미적으로 조금 더 명확하게 명시하기 위해서 변수앞에 _(underscore)붙이는 것을 컨벤션으로 사용한다.
 

5.3.1.2 접근제한자를 사용하는 이유

예시1 과 같이 클래스 외부에서 프로퍼티에 접근이 가능하게 된다면, Dog클래스로 만들어진 객체(dog)의 나이에 음수가 들어가게 되는 경우가 발생할 수 있다. 이 경우에는 문법적인 오류는 없지만 논리적인 오류가 발생하는 케이스에 속한다. 이런 상황을 대비해 미리 접근제한자를 사용하여 외부에서의 접근이 불가능하게 만들고 예시2 와 같이 서드로만 프로퍼티를 접근하게 만들어 음수가 들어오는 경우를 메서드 내부에서만 처리할 수 있게 해준다. 간단하게 보자면 프로퍼티의 접근성 측면에서 안전장치를 설치하는것이라고 생각하면 된다.
 
  • 예시1
class Dog { name?: string; age?: number; gender?: string } let dog = new Dog(); dog.age = -1; //논리적으로 오류 발생하게 된다
 
  • 예시2
class Dog { protected name!: string; private _age!: number; protected gender!: string; // 생성자를 이용하거나 초기화 해주시 않은 경우 에러 public setAge(age: number) : boolean{ // 메서드 내부에서 음수가 들어오지 못하게 설정을 하게 해준다. if (age < 0) { return false; } this._age = age; return true; } } let dog = new Dog(); dog._age = -1; // 객체의 프로퍼티가 private로 설정되어있기 때문에 접근불가 dog.setAge(-1); // 메서드내부에서 음수를 받은 경우 // 객체의 프로퍼티에 할당하지 않고 false를 반환 dog.setAge(3); // true를 반환하면서 객체의 프로퍼티에 값을 할당
 

5.3.1.3 생성자란?

constructor라는 이름의 메서드를 활용해서 생성자 역할을 할 수 있다. 객체의 프로퍼티들을 각각 초기화 시켜주는 작업이 매우 번거롭고 객체를 여러개 생성하게 된다면 코드가 복잡해지는 단점도 존재한다. 이러한 문제를 생성자 메서드를 통해서 해결할 수 있다. 객체를 생성하는 동시에 매개변수를 보내 자동으로 생성자 메서드를 실행시켜 객체의 프로퍼티에 값을 할당시켜줄 수 있다. 일종의 초기화 작업을 진행하는 것과 동일하다.
 
⚠️
TypeScript에서는 생성자 메서드의 매개변수 바로 앞에 각각 접근 제한자를 작성하게 된다면 암묵적으로 선언과 초기화를 동시에 해준다.
 
객체의 프로퍼티를 초기화하는 방법에는 아래의 코드들과 같이 다양한 방법이 존재한다.
 
  • 내부에 프로퍼티 선언 후 생성자 사용
1) 접근제한자를 활용한 프로퍼티 선언
class Dog { // 클래스 내부에 프로퍼티 선언 public name: string; private _age: number; protected gender: string; constructor(name: string, age: number, gender: string) { this.name = name; this._age = age; this.gender = gender; } }
 
2) 접근제한자를 활용하지 않은 프로퍼티 선언
class Dog { name: string; age: number; gender: string; constructor(name: string, age: number, gender: string) { this.name = name; this.age = age; this.gender = gender; } }
 
  • 생성자메서드의 매개변수에 Acess Modifier(접근 제한자)를 사용하는 경우
타입 스크립트에서는 생성자의 매개변수에 접근 제한자를 붙이게 된다면 매개변수의 이름을 가진 프로퍼티가 클래스에 선언된 것처럼 동작한다. 프로퍼티를 따로 클래스 내에서 선언을 하지 않아도 된다는 장점이 존재하게 된다. 위와 같은 코드들을 함축해서 구현할 수 있게 된다. 클래스에서 생성자를 활용하여 인스턴스의 프로퍼티들을 쉽게 선언하고 초기화 할 수 있는 방식이다.
// 매개변수부분에 접근제한자를 붙인 경우 자동으로 class 프로퍼티로 변경된다. class Dog { constructor( public name: string, private _age: number, protected gender: string ) { /* 생성자 내부에 작성하는 부분을 생략할 수 있다는 큰 장점이며 굳이 작성을 하여도 에러가 발생하지는 않는다.*/ // this.name = name; // this._age = age; // this.gender = gender; } }
 
  • 내부에 프로퍼티를 선언하지 않고 생성자를 사용할 경우 - 에러 발생
생성자에서 프로퍼티에 접근제한자를 붙이지 않는 경우 생성자메서드 내부에서만 사용되는 지역변수로 밖에 사용할 수 없기 때문에 클래스 내부에 name,age,gender 프로퍼티들이 존재하지 않는다는 오류를 발생시킨다.
class Dog { constructor(name: string, age: number, gender: string) { this.name = name; this.age = age; this.gender = gender; } }
아래와 같이 프로퍼티가 존재하지 않는다는 오류메시지를 보여준다.
notion imagenotion image
 

5.3.1.4 Class를 활용한 객체 생성 방법

 
  • 타입을 명시해준 경우
let dog : Dog = new Dog("Happy", 3, "female"); console.log(dog);
 
  • 타입을 명시해주지 않은 경우
let dog = new Dog("Happy", 3, "female"); console.log(dog);
타입스크립트에서 자동으로 type을 지정해주기 때문에 type을 명시해줄 필요가 없다. 하지만 조금 더 알아보기위해 타입을 명시하는 것을 권장한다. 위에 코드 모두 동일하게 객체를 생성할 수 있다.
 
자동으로 타입이 결정되고 있는 이미지
notion imagenotion image
 
 

5.3.2 getter / setter 메서드

객체의 프로퍼티를 외부에서 직접 조작하는 것은 데이터의 유지보수성과 논리적오류 등 많은 문제가 발생한다. 이러한 문제를 해결하기 위해서 타입스크립트에서는 접근제한자 문법이 추가되었고 getter와 setter 메서드를 통해서 데이터를 변경하고 가져오게 하였다. 직접 getter와 setter 메서드를 구현할 수 있지만 자주 사용하는 메서드이기 때문에 쉽게 구현할 수 있게 set, get 키워드가 존재한다. getter와 setter는 자바스크립트에서도 사용할 수 있지만 접근제한자와 연관된 중요한 문법이기 때문에 한번 알아보도록 하자.
 
  • setter메서드
메서드를 통해 프로퍼티를 변경하는 경우에 사용되는 메서드이며 검증코드를 내부에 작성할 수 있다.
set ratingfunc(rating: number) { // 검증코드 작성 }
 
  • setter메서드 호출방법
일반적인 메서드처럼 ()를 붙이지 않고 호출하며 일반 객체의 프로퍼티에 할당하는 것처럼 사용하면 된다.
// 일반적인 메서드 호출 객체.메서드() // set메서드 호출 객체.메서드명 = 할당할 값
 
  • getter메서드
메서드를 통해 프로퍼티를 가져오는 경우에 사용되는 메서드이다.
get ratingfunc(): number { return this._rating; }
 
  • gette메서드 호출방법
일반적인 메서드처럼 ()를 붙이지 않고 호출하며 객체의 프로퍼티를 불러오는 것처럼 사용하면 된다.
// 일반적인 메서드 호출 객체.메서드() // get메서드 호출 객체.메서드명
메서드명은 getter와 setter 모두 동일하게 구현을 하면 된다.
 
⚠️
명확하게 알아야 될 것은 private로 설정된 프로퍼티는 외부에서 접근이 불가능하다는 것이다. setter와 getter를 메서드를 클래스 내부에 만들고 호출할 경우 마치 직접 객체의 프로퍼티에 접근하고 있는 것처럼 보인다. 하지만 실제로는 직접 접근하는 것이 아니고 setter와 getter메서드에서 프로퍼티를 변경하고 가져오는 것이다.
 
  • 전체 예시 코드
class GameReview { // public title: string; // public genre: string; // public developer: string; private _rating: number; constructor(num: number) { this._rating = num; } // getter 메서드 get ratingfunc(): number { return this._rating; } // setter 메서드 - 내부에 프로퍼티의 값을 변경할 수 있는지 확인하는 검증코드 작성 set ratingfunc(rating: number) { if (rating < 1 || rating > 6) { return; } this._rating = rating; } } //객체 생성 let review = new GameReview(0); // setter 함수 활용 review.ratingfunc = 4; // getter 함수 활용 console.log(review.ratingfunc);
 
 

5.3.3 추상클래스

 

5.3.3.1 추상클래스란?

미리 공통된 프로퍼티나 메서드의 설계도를 만들기 위해 사용되는 클래스이다. 자식 클래스에게 상속을 시키면서 유지보수성과 시간의 효율성을 높일 수 있기 때문에 사용하며 객체지향프로그래밍에서의 중요한 특징인 다형성을 가지는 메서드의 집합을 정의 할 수 있도록 해준다.
추상클래스를 만들기 위해서는 class앞에 abstract 라는 키워드를 붙여야 되며 추상클래스는 미완성된 설계도인 클래스이기 때문에 추상클래스 자체적으로는 객체를 생성할 수 없고 자식 클래스에게 상속만 가능하다.
 
  • 예시1
추상클래스로는 객체를 생성할 수 없다.
abstract class Phone { constructor(public owner: string){} public callConnect() : void{ console.log("따르릉"); } } // 추상클래스로는 객체를 생성할 수 없다. let phone = new Phone("이정호"); // error
 
  • 예시2
상속을 하기 위해서는 extends 키워드를 사용해야 된다. 위에서 시간의 효율성을 높일 수 있다고 했는데 그말은 즉 공통된 추상클래스를 작성한 후 상속을 시켜줄 경우 두개의 클래스를 따로따로 작성하는 것보다 시간을 줄일 수 있기 때문이다. 그리고 아래의 코드처럼 동일한 메서드명와 프로퍼티명을 통일할 목적인 경우에도 추상클래스를 사용한다.
// 공통된 프로퍼티나 메서드를 미리 설계한 추상클래스 abstract class Phone { constructor(public owner: string){} public callConnect() : void{ console.log("따르릉"); } } // 각각 CellPhone과 TelePhone 클래스에게 상속 class CellPhone extends Phone { constructor(owner:string){ super(owner) } } class TelePhone extends Phone { constructor(owner:string){ super(owner) } } let cellphone = new CellPhone("이정호"); cellphone.callConnect(); // 따르릉 let telephone = new TelePhone("이정호"); telephone.callConnect(); // 따르릉
 
  • 일반 클래스를 상속시켜주지 않고 추상클래스를 만들어서 상속시켜주는 이유
일반 클래스는 객체생성이 가능하고 추상클래스는 객체생성이 불가능하다는 차이점도 있지만 추상메서드를 사용하면서 두개의 차이점은 더 명확해진다.
 

5.3.3.2 추상 메서드

추상메서드는 선언은 되어 있으나 코드가 구현되어 있지 않은 메서드를 의미한다. 만약 클래스에 추상메서드가 한개라도 존재하게 되면 일반 클래스가 아닌 추상클래스를 선언해야 된다.
 
  • 예시를 통해 살펴본 추상 메서드 사용 방법
동물이 소리를 내는 공통적인 행동이라는 것을 알고있으나 동물들 마다 내는 소리가 다르기 때문에 완벽하게 공통된 메서드를 구현하기는 힘들다. 이런 경우에는 메서드의 선언부만 추상클래스에 만들고 구체적인 메서드의 정의는 상속받는 자식클래스에게 역할을 넘겨준다.추상메서드가 있는 추상클래스를 상속받은 자식 클래스는 무조건 추상메서드를 정의해야 된다. 아니면 오류를 발생시킨다.
abstract class Animal { constructor(public name: string) {} // 추상클래스에서는 선언만 진행 public abstract sound() : void; } class Cow extends Animal { constructor(name: string) { super(name); } //메서드를 구현하지 않은 경우 오류 발생 무조건 구현해야 된다. public sound() : void { console.log("음메"); } } class Duck extends Animal { constructor(name: string) { super(name); } //메서드를 구현하지 않은 경우 오류 발생 무조건 구현해야 된다. public sound() : void { console.log("꽥꽥") } } let duck = new Duck("리버덕스"); duck.sound(); let cow = new Cow("맥스"); cow.sound();
 
 

5.3.3.3 추상클래스와 인터페이스

추상클래스
인터페이스
프로퍼티와 메소드
실제 구현이 되어 있는 일반 메서드와 초기화 된 프로퍼티를 가질 수 있다.
일반메서드를 포함 할 수 없고 추상 메서드만 가질 수 있다.
상속
클래스의 종류 중 하나이기 때문에 한개의 클래스만 상속이 가능하다.
다중으로 상속이 가능하다.
클래스에 인터페이스를 활용하는 방법에 대해서는 인터페이스 목차인 클래스에서 인터페이스 활용에서 후술된다.
 
  • 예시1
아래의 코드는 추상클래스를 다중으로 자식 클래스에 상속 시킬 수 없는 것을 보여주는 예시코드이다.
abstract class Player { constructor(public backnumber: number) {} } abstract class People { constructor(public name: string) {} public move() : void { console.log("걷기"); } } // 인터페이스와 다르게 클래스는 다중 상속을 받는 것이 불가능하다 class SoccerPlayer extends People, Player { } // error
 
 

5.3.4 static

인스턴스의 생성과 무관하게 어떤 프로퍼티나 메서드를 사용하기 위해 static 키워드를 사용한다.
static키워드는 클래스에 정적 프로퍼티나 정적 메서드 등을 선언할 때 사용할 수 있는데 객체 생성 없이 바로 클래스명으로 접근이 가능하므로 메모리 절약 효과가 있다. 또한 클래스와 인스턴스 간에 공통으로 사용되어야 할 프로퍼티나 중복이 되어있는 프로퍼티가 있다면 static키워드를 사용하면 된다.
자바스크립트 ES6문법에서는 정적 메서드가 사용가능하지만 정적 프로퍼티는 브라우저의 호환성에 따라 오류 유무가 결정된다. 하지만 TypeScript에서는 정적 메서드 정적 프로퍼티 모두 정상적으로 사용가능하다. 정적 프로퍼티와 정적 메서드는 생성한 객체명이 아닌 클래스이름으로만 호출이 가능하다. 생성된 인스턴스로 클래스의 정적 프로퍼티를 가져오기 위해서는 클래스 내부에 getter메서드를 만들어 가져올 수 있게 하면 된다.
 
예시코드를 보면서 정적 프로퍼티, 정적 메서드, 인스턴스 프로퍼티, 일반 메서드가 접근이 가능한지 확인해보자
class Test { // 인스턴스 간에 공통으로 프로퍼티를 공유하기 위해서 static키워드 사용 static staticproperty: string = "static"; // 인스턴스 프로퍼티는 객체들마다 다른 값을 가지고 있다. public instanceproperty: string; constructor(property: string) { this.instanceproperty = property; } // 정적 메서드 static staticMethod(): void { // 아래와 같이 정적 메서드에서는 인스턴스 프로퍼티를 사용할 수 없다는 점을 유의하자 /* 이유는 외부에서 클래스이름으로 정적 메서드를 호출을 한 경우 어떤 인스턴스의 프로퍼티인지를 알 수 없기 때문에 사용할 수 없게 만들었다.*/ /* 정적 메서드에서의 this는 클래스 그 자체를 의미한다. */ console.log(this.instanceproperty); // error console.log(Test.staticproperty); console.log(this.staticproperty); } // 일반 메서드 instanceMethod(): void { // 반대로 인스턴스 메서드에서는 정적 프로퍼티를 사용할 수 있다. console.log(this.instanceproperty); console.log(Test.staticproperty); } } console.log(Test.staticproperty); // 인스턴스 생성 let test = new Test("instance"); //인스턴스로 정적프로퍼티 접근이 불가능하다. console.log(test.staticproperty); // error //클래스이름으로 프로퍼티 접근이 불가능하다. console.log(Test.instanceproperty); // error // 클래스이름.정적메서드 Test.staticMethod(); // 클래스이름.일반메서드 Test.instanceMethod(); // error // 인스턴스.일반메서드 test.instanceMethod(); // 인스턴스.정적메서드 test.staticMethod(); // error
 
  • 정적 메서드에서 프로퍼티에 접근한 경우와 클래스명을 통해 일반메서드와 프로퍼티 접근한 경우 오류가 발생하는 이유
notion imagenotion image
클래스를 통해서 정적메소드에 접근이 가능하기 때문에 만약 정적 메소드안에서 프로퍼티에 접근이 가능하게 된다면 생성된 객체 중 어떤 객체의 프로퍼티인지 알 수 없는 문제가 발생하게 된다. 동일한 이유로 외부에서 클래스명을 통해 일반 메서드와 프로퍼티의 접근이 불가능하게 된다.
 

5.3.4.1 정적 프로퍼티 활용

정적 프로퍼티가 언제 사용되는 지 예시코드들을 보면서 확인해보록 하자
 
  • 예시1
생성된 객체마다 cutline이라는 프로퍼티를 가지게 된 경우 메모리 낭비가 발생한다. 모두 공통된 프로퍼티를 가지고 싶은 경우에는 static키워드 를 사용하여 정적 프로퍼티로 만들면 된다.
class JsExam { static cutline: number = 60; constructor(public name: string, public score: number) {} public checkScore(): void { if (this.score > JsExam.cutline) { console.log(`${this.name}님 합격입니다.`); } else { console.log(`${this.name}님 불합격입니다.`); } } } let result_j = new JsExam("이정호", 40); let result_s = new JsExam("김성진", 80); let result_e = new JsExam("선은혜", 90); result_j.checkScore(); // 이정호님 불합격입니다. result_s.checkScore(); // 김성진님 합격입니다. result_e.checkScore(); // 선은혜님 합격입니다.
 
  • 예시2
객체 생성하면서 객체 카운트 하기
정적 프로퍼티를 모든 객체들이 공유한다는 것을 아래의 코드를 보면 확실하게 알 수 있다.
class GamePlayer { static playercount: number = 0; constructor(protected name: string) { //생성자메서드는 객체를 생성할 때 자동으로 실행되는 메서드이기 때문에 그때마다 카운트가 증가 GamePlayer.playercount++; } // getter메서드로 객체에 접근해서 정적프로퍼티를 가져올 수 있다. get count(): number { return GamePlayer.playercount; } } let player1 = new GamePlayer("jeongho"); let player2 = new GamePlayer("eunhye"); let player3 = new GamePlayer("seongjin"); // 생성된 객체들을 모두 공통된 값을 공유하게 된다. console.log(GamePlayer.playercount); // 3 console.log(player1.count); // 3 console.log(player2.count); // 3 console.log(player3.count); // 3
 
 

 

- Reference

1.콜백 함수에서의 this