헷갈리는 것들만 따로 정리할 예정.
deep dive 책을 함께 공부함.
8. 클래스
ES6에 도입.
Class : 객체를 생성할 수 있는 템플릿(양식, 틀), 객체지향 프로그래밍. 객체끼리 서로 호환 가능.
공통적인 구조를 가진 객체가 있다면 생성자 함수를 이용하여 손쉽게 객체를 만들 수 있음.
생성자 함수를 템플릿(양식)처럼 사용하여 객체를 손쉽게 만들어낼 수 있는 이유는,
자바스크립트가 프로토타입(Prototype) 객체지향 프로그래밍을 지원해주기 때문.
대부분의 객체지향 프로그래밍 언어에서는 프로토타입이 아닌 클래스(Class)를 기반으로 한 객체지향 프로그래밍을 가능하게 함.
[ 객체지향 프로그래밍 언어 ]
자바스크립트 | 대부분(Java, C#, Kotlin 등...) |
프로토타입(Prototype) 기반 | 클래스(Class) 기반 |
예전에는 프로토타입을 이용하여 비슷한 구조를 가진 객체를 만들 수 있게 했다면
이제는 Class를 이용하여 좀 더 손쉽게 객체를 만들 수 있음. (ES6 도입)
=> 요즘 객체지향 프로그래밍 언어라고 하면 대부분 Class를 기반으로 해서 많이 다룸.(자바스크립트도)
=> 요즘 빈도수가 더 높은 것 : 생성자 함수(Prototype) < 클래스(Class)
=> 이제 현업이나 타입스크립트에서는 생성자 함수를 거의 사용하지 않음.
그냥 Class를 이용해도 자바스크립트에서는 내부적으로 프로토타입을 쓰기 때문에
문법적으로 Class인 것처럼 보이도록 syntax sugar(신텍스 슈가)가 프로토타입을 살짝 감싸고 있음.
- 인스턴스(Instance) : Class를 통하여 만들어진 객체
8-1) Class 기본 문법
- class 템플릿은 중괄호로 만들어줘야 함.
- Fruit class를 이용해서 만드는 객쳋의 필요한 데이터를 채워줄 수 있는 생성자(constructor) 지정.
class Fruit {
constructor(name, emoji) {
// 생성자 : new 키워드로 객체를 생성할 때 호출되는 함수
this.name = name;
this.emoji = emoji;
}
// class에 필요한 함수는 보통 생성자 밖에서 정의함.
// this 붙일 필요 없이 함수 이름만 정의하면 됨.
// function 키워드를 붙이면 오류 발생.
display = () => {
console.log(`${this.name} : ${this.emoji}`);
};
}
const apple = new Fruit("apple", "🍎");
const orange = new Fruit("orange", "🍊");
console.log(apple);
console.log(orange);
console.log(apple.name); // apple
console.log(orange.name); // orange
console.log(apple.emoji); // 🍎
console.log(orange.emoji); // 🍊
apple.display(); // apple : 🍎
orange.display(); // orange : 🍊
8-2) 재사용성 높이는 방법
Class에 정의된 프로퍼티와 메소드는 인스턴스 레벨의 프로퍼티와 메소드라고 볼 수 있음.
객체에 Class에서 정의한 프로퍼티와 함수들이 중복적으로 만들어지기 때문.
class Fruit {
this.name = name;
this.emoji = emoji;
display = () => {
console.log(`${this.name}은 ${this.emoji}`);
}
}
만약 모든 객체마다 동일하게 참조해야 하는 속성이나 행동(메소드)이 있다면
Class 레벨의 프러퍼티와 메소드를 이용하면 됨.
프로퍼티나 메소드 앞에 static을 붙일 수 있음.
=> 만들어진 인스턴스에 포함되지 않고 class에 그대로 남아있게 됨. (class에 한 번만 만들어지게 됨.)
class Fruit {
this.name = name;
this.emoji = emoji;
static make(){
console.log(`${this.name}은 ${this.emoji}다.`);
}
}
Class 레벨의 메소드를 호출하기 위해서는 Fruit.make(); 이런식으로 Class 자체에 접근을 해야 함.
[ 프로퍼티, 메소드에 접근하는 방법 ]
인스턴스 레벨의 프로퍼티나 메소드 | 클래스 레벨의 프로퍼티나 메소드 |
반드시 생성된 인스턴스를 통해서 접근 | 반드시 클래스를 통해서 접근 |
class Fruit {
// static 정적 프로퍼티, 메서드 => 클래스 레벨 => 클래스 별로 공통적으로 사용
// 속성 앞에 static 메서드를 붙일 수 있음
// 만들어진 인스턴스 데이터에 참조한 필요가 없는 함수라면 static으로 만들 수 있음
// Fruit이라는 class 이름을 통해서만 접근이 가능. (인스턴스 레벨에서는 접근 X)
static MAX_FRUITS = 4;
constructor(name, emoji) {
this.name = name;
this.emoji = emoji;
}
static makeRandomFruit() {
// 클래스 레벨의 메서드에서는 주어진 데이터가 채워지지 않은 상태의 템플릿이기 때문에 this를 참조할 수 없음.
// ( class 자체로는 아무것도 채워지지 않았기 때문 )
return new Fruit("banana", "🍌");
}
// 인스턴스 레벨의 메서드
// 만들어진 object의 데이터에 접근하여 출력해야 하기 때문에 인스턴스와 밀접하게 연관이 있으므로
// 그대로 인스턴스 레벨로 두는 게 좋음.
display = () => {
// display () { } => 이런식으로 써도 됨
console.log(`${this.name}: ${this.emoji}`)
};
};
const banana = Fruit.makeRandomFruit();
console.log(banana);
console.log(Fruit.MAX_FRUITS);
❌❌ console.log(Fruit.name); ❌❌
❌❌ Fruit.display(); ❌❌
// 객체 자체에는 데이터들이 채워져 있지 않아 이 자체만으로는 호출이 불가능
// 속성에 접근해도 데이터가 없는 상태
const apple = new Fruit("apple", "🍎");
const orange = new Fruit("orange", "🍊");
console.log(apple);
console.log(orange);
console.log(apple.name);
console.log(apple.emoji);
// static이라는 함수는 만들어진 인스턴스 안에는 포함되어 있지 않고
// class Fruit이라는 이름을 통해서만 접근이 가능
ex) static 사용 예시
1. Math.pow(); => 빌트인 오브젝트
// Math라는 것을 이용해서 object를 만들지 않아도, 바로 유용한 뉴틸리티 함수들을 이용 가능.
2. Number.InFinite(1);
// 바로 함수에 접근 가능.
8-3) 필드
- class와 constructor 사이의 공간이 필드(filed).
- constructor에서 주어지는 데이터라면 필드에서 생략 가능
- type = "과일";로 지정을 해주면 class가 만들어지자마자 type이 "과일"로 미리 초기화 됨.
// 접근제어자 - 캡슐화
// 다른 프로그렘 언어에는 private, public, protected 키워드가 있음.
// 자바스크립트에는 이런 키워드가 없고, 기본적으로 아무것도 사용하지 않으면 모두 공개, 접근 가능한 상태.
// 외부에서 접근을 못하게 하려면 private : #이라는 키워드를 사용하면 됨.
class Fruit {
#name;
#emoji; // constructor에서 주어지는 데이터라면 필드에서 생략 가능
type = "과일"; // type은 class가 만들어지자마자 과일로 초기화가 미리 되어있음.
constructor(name, emoji) {
this.#name = name;
this.#emoji = emoji;
}
display = () => {
// display (){ } => 이런식으로 써도 됨
console.log(`${this.#name}: ${this.#emoji}`);
};
}
const apple = new Fruit("apple", "🍎");
const orange = new Fruit("orange", "🍊");
// 한 번 객체가 만들어진 다음에 이렇게 외부에서 수정이 불가능하게 하고싶음.
// apple의 name이 오렌지인건 이상함.
// apple.#name = "orange"; => #filed는 외부에서 접근이 불가능함
console.log(apple);
// 우리가 인스턴스에 만들어진 것을 출력할 때도 #filed는 뜨지 않음.
8-4) 세터(setter)와 게터(getter)
- 접근자 프로퍼티(Accessor Property) : 변수처럼 보이지만 실제로는 함수. => 게터(getter), 세터(setter)
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
fullName() {
return `${this.lastName} ${this.firstName}`;
}
}
const student = new Student("수지", "김");
console.log(student.fullName());
// 김 수지가 출력됨.
// => 행동을 하는게 아니고 이름이라는 상태를 보여주는 건데 왜 함수처럼 호출을 하는가? 이상하다.
- 이렇게 상태를 나타내는데 함수를 호출해서 사용하는게 싫다면, 함수 대신 프로퍼티로 대신하면 되지 않는가?
그래서 함수 대신 프로퍼티로 만들어봄.
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${this.lastName} ${this.firstName}`;
}
}
const student = new Student("수지", "김");
student.firstName = "안나";
console.log(student.fullName);
// firstName을 "안나"로 바꿨지만 fullName은 여전히 "김 수지"로 출력됨.
// fullName을 프로퍼티로 지정해두면 생성자가 호출된 순간 벌써 fullName이 지정됨.
// 계속해서 업데이트가 이뤄지지 않음.(업데이트가 이뤄지려면 함수를 사용해야 함.)
이렇게 프로퍼티로 fullName을 옮겨본 결과, 계속해서 업데이트가 이뤄지지 않는다는 것을 알 수 있었음.
그렇다면 함수도 프로퍼티도 사용할 수 없다면 우리는 무엇을 해야 할까?
이럴 때 사용하는 것이 바로 접근자 프로퍼티(getter, setter)임.
계속해서 업데이트가 이뤄지길 바라고 fullName(); 이렇게 함수로 호출하는 것도 싫을 때 사용.
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.lastName} ${this.firstName}`;
}
set fullName(value) {
console.log(value);
}
}
const student = new Student("수지", "김");
console.log(student.fullName);
// fullName 함수 앞에 get을 붙여주면 student.fullName;으로 접근하여 "김 수지"를 출력할 수 있음.
student.fullName = "김철수";
// fullName 함수 앞에 set을 붙여주면 특정한 value 값 할당시 set 함수가 호출되어 "김 철수"가 출력됨.
8-5) 상속
class마다 공통된 속성이나 행동들이 있다면 상속(extends)을 사용할 수 있음.
class Tiger {
constructor(color) {
this.color = color;
}
eat() {
console.log("먹자!");
}
sleep() {
console.log("잔다");
}
}
class Dog {
constructor(color) {
this.color = color;
}
eat() {
console.log("먹자!");
}
sleep() {
console.log("잔다");
}
play() {
console.log("놀자아~!");
}
// Dog에만 play();라는 함수를 추가해줄 거임.
}
상속을 사용하면 class마다 동일한 걸 반복적으로 작성할 필요가 없음.
// 상속받고자 하는 부모의 class를 만들기.
class Animal {
constructor(color) {
this.color = color;
}
eat() {
console.log("먹자!");
}
sleep() {
console.log("잔다");
}
}
// extends라는 키워드를 사용해서 Animal 클래스를 그대로 상속받을 수 있음.
// 아무것도 추가하거나 바뀌지 않는 경우 중괄호 안을 비워두면 됨.
class Tiger extends Animal {}
const tiger = new Tiger("노랑이");
console.log(tiger);
tiger.sleep();
tiger.eat();
console.log(tiger.color);
console.log(tiger);
// class Dog처럼 무언가를 추가하고 수정하려면 중괄호 안에 내용을 추가해주면 됨.
class Dog extends Animal {
// 주인의 이름도 추가하고 싶다, 라고 한다면 constructor을 이용해서 외부에서 가져오면 됨.
// 자식 class에서 constructor을 정리하는 순간, Animal에 필요한 것들도 다 받아와야 함.
constructor(color, ownerName) {
// super => 내가 상속하고 있는 부모를 가리킴
super(color);
this.ownerName = ownerName;
}
play() {
console.log("놀자아~!");
}
// 부모 클래스에 있는 eat();라는 함수를 class dog에서는 조금 다르게 작동시키고 싶다면, 다시 덮어씌우면 됨.
// 오버라이딩(overriding)
eat() {
super.eat(); // 먼저 부모의 eat(); 함수를 호출한 후에 내가 하고 싶은 걸 할 수 있음.
console.log("강아지가 먹는다!");
}
}
const dog = new Dog("빨강이", "앨리");
console.log(dog);
dog.sleep();
dog.eat();
dog.play();
'(심층)자바스크립트' 카테고리의 다른 글
다시 시작하는 자바스크립트 - 클래스 퀴즈2 (0) | 2023.03.15 |
---|---|
다시 시작하는 자바스크립트 - 클래스 퀴즈1 (0) | 2023.03.14 |
다시 시작하는 자바스크립트 - 객체 (3) | 2023.03.13 |
(심층)자바스크립트다시 시작하는 자바스크립트 - 함수 (0) | 2023.03.11 |
(심층)자바스크립트다시 시작하는 자바스크립트 - 반복문 for 제어 : continue, break (0) | 2023.03.11 |
github : https://github.com/dnjfht
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!