이넘, 클래스
이넘(enum)은 특정 값의 집합을 의미하는 데이터 타입이다.
상수 집합이라고도 표현한다.
function getDinnerPrice() {
const RICE = 10000;
const COKE = 2000;
return RICE + COKE;
}
상수는 단순히 고정된 값을 저장하는 것뿐만 아니라 이 값이 어떤 의미를 갖는지 알려 줌으로써 가독성을 높이는 장점이 있다. 여러개의 상수를 하나의 단위로 묶으면 이넘이 된다. 비슷한 성격이나 같은 범주에 있는 상수를 하나로 묶어 더 큰 단위의 상수로 만드는 것이 이넘의 역할이다.
enum ShoesBrand {
Nike,
Adidas,
NewBalance
}
var myShoes = ShoesBrand.Nike;
var yourShoes = ShoesBrand.NewBalance;
객체 속성에 접근하듯 이넘의 이름을 쓰고 . 접근자를 이용하여 속성 이름을 붙인다.
그럼 myShoes와 yourShoes에는 각각 0과 1이 대입된다.
이넘에 선언된 속성은 기본적으로 숫자 값을 가진다.
다음과 같이 이넘을 선언하면 첫번째 속성부터 0, 1, 2, 3이 할당된다.
enum Direction {
Up, //0
Down, //1
Left, //2
Right //3
}
console.log(Direction.Up); //0
console.log(Direction[0]); //'Up'
이렇게 이넘의 속과 값이 거꾸로 연결되어 할당되는 것을 리버스 매핑(reverse mapping)
이라고 한다.
이처럼 타입스크립트의 이넘에 선언된 속성은 기본적으로 숫자 값을 갖는다.
이넘 속성의 초기값을 변경하고 싶다면 다음과 같이 선언한다.
enum Direction {
Up = 10, //10
Down, //11
Left, //12
Right //13
}
이 코드는 첫 번째 Up 속성의 오른쪽에 = 기호를 사용하여 10이라는 값을 할당한다.
첫번째 속성의 시작값을 변경하더라도 순서대로 선언된 이넘 속성의 값은 1씩 증가하는 규칙이 있다.
다만 명시적으로 값을 설정하는 것이 컴파일 결과를 보지 않고도 값을 빠르게 파악할 수 있어 좋다.
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
숫자형 이넘과 다르게 모든 속성 값을 다 문자열로 지정해 주어야하고 선언된 속성 순서대로 값이 증가하는 규칙도 없다.
실무에서는 이넘 값을 숫자로 관리하기보다 문자열로 관리하는 사례가 더 많다.
그리고 속성 이름과 값을 동일한 문자열로 관리하는 것도 일반적인 코딩 규칙이다.
파스칼 케이스 뿐만아니라 대문자로 적거나 언더스코어를 사용해도 괜찮다.
enum Answer {
Yes = 'Yes',
No = 1
}
이넘을 다음과 같이 숫자와 문자열을 섞어서 선언할 수 있지만 이넘 값은 일괄 되게 숫자나 문자열 둘 중 하나의 데이터 타입으로 관리하는 것이 좋다. 불필요하게 속성 별로 어떤 값이 들어 있는지 기억할 필요는 없다.
enum Authorization {
User, //0
Admin, //1
SuperAdmin = User + Admin, //1
God = "abc".length //3
}
User과 Admin 속성은 이넘의 기본 규칙에 따라 값이 0과 1이다. 그리고 SuperAdmin 속성은 User과 Admin 의 값을 더한 결과인 1을 갖는다. 마지막으로 abc 문자열의 길이는 3이므로 God 속성 값은 3이 된다. 이처럼 이넘 속성 값을 할당할 때 연산자 등을 활용할 수 있지만 활용도는 높지 않다. 앞서 문자형 이넘에서 안내했듯이 각 속성 이름만 보고도 값을 바로 추측할 수 있는 문자열 값을 많이 사용하기 때문이다.
const enum logLevel {
Debug = 'Debug',
Info = 'Info',
Error = 'Error'
}
var appLevel = logLevel.Error
const는 변수를 선언할 때 사용하는 예약어이다. 이 예약어는 이넘 타입을 정의할 때도 사용할 수 있다.
const를 이넘 앞에 붙이는 이유는 컴파일 결과물의 코드양을 줄이기 위해서이다.
const 이넘은 컴파일 될 때 객체가 이넘의 속성 이름과 값을 연결해주는 객체를 생성하지 않고 이넘이 사용되는 곳에서 속성값을 바로 연결해준다.
클래스란 여러가지 유사한 객체를 쉽게 생성하는 자바스크립트 최신 문법(ES6+)이다.
var capt = {
name: '캡틴',
skill: '방패 던지기'
};
var lee = {
name: '길벗',
skill: '좋은 책 만들기'
}
이 두 객체 모두 name과 skill이라는 공통된 속성을 가지고 잇다.
이렇게 모양이 유사한 객체는 일일이 객체를 정의하기보다 생성자 함수를 사용하는것이 유리하다.
function Person(name, skill) {
this.name = name;
this.skill = skill;
}
var capt = new Person ('캡틴', '방패 던지기');
var lee = new Person ('길벗', '좋은 책 만들기');
함수의 첫 글자를 대문자로 작성하면 생성자 함수라고 보는 것이 일반적인 관례이다.
생성자 함수는 new라는 키워드를 붙여서 호출하면 새로운 객체를 생성해준다.
생성자 함수는 new라는 키워드를 붙여서 호출하면 새로운 객체를 생성해 준다.
이렇게 객체를 쉽게 찍어 내는 함수가 생성자 함수이다.
이 생성자 함수를 최신 자바스크립트 문법으로 표현하면 다음과 같다.
class Person {
construnctor(name, skill) {
this.name = name;
this.skill = skill;
}
}
function Person(name, skill) {
this.name = name;
this.skill = skill;
}
Person.prototype.sayHi = function() {
console.log('hi');
}
var joo = new Person("형주", "인프랩 운영");
console.log(joo.name); // 형주
console.log(joo.skill); // 인프랩 운영
joo.sayHi(); // hi
이 코드는 Person() 생성자 함수에 sayHi라는 속성 함수(속성 값으로 함수가 연결된 속성)를 추가한다.
속성 함수를 함수 안에 선언하지 않고 Person.prototype.sayHi 형태로 선언한 이유는 바로 이어질 클래스 코드와 비교하기 위해서이다. 이 생성자 함수로 객체를 하나 생성해보면 생성자 함수의 이름에 형주라는 문자열을 넣고, 기술에 인프랩 운영이라는 문자열을 넣어 새로운 객체를 생성한다. 생성된 객체는 joo 변수에 담겨있다.
생성된 객체의 속성과 속성 함수는 다음과 같이 출력하여 사용할 수 있다.
class Person {
constructor(name, skill) {
this.name = name;
this.skill = skill;
}
sayHi() {
console.log('hi');
}
}
var joo = new Person("형주", "인프랩 운영");
이 Person 클래스 코드는 name과 skill 값을 받아 객체를 생성할 수 있게 생성자 메서드(constructor)
를 선언하고, sayHi()라는 클래스 메서드(class method)
를 선언한 코드이다.
name과 skill 속성을 클래스 필드(class field)
또는 클래스 속성(class property)
이라 한다.
클래스도 앞의 생성자 함수와 동일하게 new 키워드를 붙여 객체를 생성한다.
이렇게 클래스로 생성된 객체를 클래스 인스턴스(class instance)
라고 한다.
클래스의 상속(inheritance)이란 부모 클래스의 속성과 메서드 등을 자식 클래스에서도 사용할 수 있게 물려준다는 의미이다.
class Person {
constructor(name, skill) {
this.name = name;
this.skill = skill;
}
sayHi() {
console.log('hi');
}
}
class Developer extends Person {
constructor(name, skill) {
super(name, skill);
this.sayHi();
}
coding() {
console.log('fun doing' + this.skill + 'by ' + this.name);
}
}
이 코드는 Person 클래스를 상속받아 Developer 클래스를 정의한다.
Developer 클래스의 생성자 메서드를 보면 super(name, skill); 코드가 있다.
이 코드는 자식 클래스인 Developer 클래스에서 new 키워드로 객체를 생성할 때 부모 클래스인 Person 클래스의 생성자 메서드를 호출하겠다는 의미다.
var capt = new Developer('캡틴','방패 던지기');
capt.coding(); // fun doing방패 던지기by 캡틴
capt.sayHi(); // hi
console.log(capt.name); // 캡틴
console.log(capt.skill); // 방패 던지기
Developer 클래스로 새로운 객체(클래스 인스턴스)를 하나 생성해봤다.
이 코드는 클래스로 생성한 객체를 capt 변수에 담고 클래스 메서드 coding()을 호출한다.
coding() 메서드를 호출하면 fun이 콘솔에 출력된다.
부모 클래스 Person에 정의된 sayHi() 메서드도 정상적으로 호출할 수 있다.
상속 받은 메서드 뿐만아니라 클래스 속성에도 접근할 수 있다.
자식 클래스인 Developer의 생성자 메서드에 부모 클래스의 메서드인 this.sayHi(); 를 정의했기 때문에 콘솔창에 hi가 출력된 뒤 name과 skill 속성을 this.skill과 this.name으로 잘 가져오는 것을 확인할 수 있다.
이렇게 클래스를 상속받으면 기존 클래스에 정의된 속성과 메서드를 재활용할 수 있어 객체 지향 프로그래밍에 유리하다.
클래스에 타입 정의하는 방법
class Chatgpt {
constructor(name) {
this.name = name;
}
sum(a, b) {
return a + b;
}
}
var gpt = new Chatgpt('대화형 AI');
gpt.sum(10,20); // 30
gpt.name; // 대화형 AI
이 코드는 Chatgpt라는 클래스 안에 생성자 메서드와 클래스 메서드 sum()을 선언한다.
클래스를 생성할 때 name값을 받아서 새로운 객체를 생성할 수 있다.
class Chatgpt {
name: string;
constructor(name: string) {
this.name = name;
}
sum(a: number, b: number): number {
return a + b;
}
}
이 클래스 코드에 타입을 입혀보면 생성자 메서드 함수의 파라미터인 name의 타입은 string으로 정의했고, sum() 클래스 메서드 함수의 파라미터와 반환 타입은 모두 number로 정의했다.
타입스크립트로 클래스를 작성할 때는 생성자 메서드에서 사용될 클래스 속성들을 미리 정의해 주어야한다.
이렇게 자바스크립트 클래스에 타입을 추가하여 타입스크립트 클래스로 변환할 수 있다.
class WaterPurifier {
waterAmount: number;
constructor(waterAmount: number) {
this.waterAmount = waterAmount;
}
wash() {
if (this.waterAount > 0) {
console.log('정수기 동작 성공');
}
}
}
var purifier = new WaterPurifier(30);
purifier.wash(); // 정수기 동작 성공
purifier.waterAmount = 0;
purifier.wash();
클래스 생성자 메서드로 클래스를 생성할 때 물의 양을 입력 받을 수 있게 했다. 클래스 메서드는 wash()는 물이 조금이라도 있어야 동작하게끔 if 문을 작성했다. 이 클래스를 사용하여 객체를 생성하고 purifier라는 변수에 할당했다.
이 코드를 실행하면 물의 양이 30이기 때문에 정수기 동작 성공 이라는 문자열이 콘솔에 출력된다. 그런데 누군가가 purifier 객체에 접근하여 물의 양을 0으로 바꾸고 wash() 메서드를 실행하면 메시지가 출력되지않는다.
이러한 에러를 방지할 수 있는 속성 접근 제어자를 알아보겠다.
public
class WaterPurifier {
public waterAmount: number;
constructor(waterAmount: number) {
this.waterAmount = waterAmount;
}
public wash() {
if (this.waterAount > 0) {
console.log('정수기 동작 성공');
}
}
}
var purifier = new WaterPurifier(30);
console.log(purifier.waterAmount); // 30
purifier.wash();
public 접근 제어자는 클래스 안에 선언된 속성과 메서드를 어디서든 접근할 수 있게 한다.
클래스 속성과 메서드에 별도로 속성 접근 제어자를 선언하지 않으면 기본값은 public 이다.
private
private 접근 제어자는 클래스 코드 외부에서 클래스의 속성과 메서드를 접근할 수 없다.
public과 반대되는 개념으로 클래스 안 로직을 외부 세상에서 단절시켜 보호할때 주로 사용하다.
protected
protected로 선언된 속성이나 메서드는 해당 클래스와 자식 클래스에서는 사용할 수 있고 클래스 외부에서는 사용할 수 없다. private과 비슷하면서도 좀 더 상속에 유연한 느낌이다.