내일배움캠프 React트랙 20일차 회고 (2022.11.25)
클래스(Class)
클래스란 생성자함수와 마찬가지로 동일한 부류의 속성과 행동을 가지고있는 객체를 생성할 수 있는 템플릿(청사진, 틀)이다.
클래스를 통해 객체지향 프로그래밍(Object-Oriented Programming)이 가능하다.
자바스크립트는 프로토타입(prototype)을 베이스로한 객체지향언어이다. 따라서 생성자함수를 통해 객체를 찍어내는 행위가 가능했다.
class가 애초에 존재하지 않았지만 class를 사용하는 여타 객체지향 언어(JAVA, Python, C++…) 에 익숙한 사람들의 수요에 의해 ES6부터 새롭게 적용 되었다. 그럼에도 불구하고 자바스크립트는 프로토타입 중심 임에는 변함 없고, class 역시도 프로토타입을 기반으로 만들어진 것이다. (syntaticsugar…) 자바스크립트로 설정한 class에 typeof() 를 입혀보면 실은 function 임을 알 수 있다.
현대 개발시장에서는 프로토타입을 기반으로한 객체지향프로그래밍은 잘 쓰이지 않으므로, 생성자함수 역시 잘 쓰이지 않는다.
*클래스를 통해 만들어진 객체를 인스턴스(Instance)라 한다.
*함수이지만 객체내에 작성된 함수를 메서드(method)라 한다.
Class 템플릿, 인스턴스 생성법
class Laptop {
// 생성자(constructor): new 키워드로 객체를 생성할때 호출되는 함수
constructor(osType, wegiht) {
this.osType= osType;
this.wegiht= wegiht;
this.calculate = () => {
console.log(`${this.osType}, ${this.wegiht}`);
};
}
}
1. function대신 class를 작성
2. 생성자(constructor) 작성
3. 클래스에 적혀지는 클래스의 멤버함수는 생성자범위 내에서 작성하는 것이 아니라, 생성자범위 밖에서 작성한다. (에러가 발생하지는 않지만 통상적으로 그렇게 작성한다.) 따라서 위의 코드보다 아래의 코드가 올바르게 작성된 class라고 볼 수 있다.
class Laptop {
constructor(osType, wegiht) {
this.osType= osType;
this.wegiht= wegiht;
}
calculate = () => {
console.log(`${this.osType}, ${this.wegiht}`);
};
}
// macBookr과 grma이란 객체는 Fruit 클래스의 인스턴스이다.
// (클래스를 이용해 만든 객체를 인스턴스라 하니까)
const macBook = new Laptop('macOs', '1.7kg');
const gram = new Laptop('windowOs', '1kg');
console.log(macBook);
console.log(gram);
console.log(macBook.osType);
console.log(gram.wegiht);
macBook.calculate();
// console.log를 통해 출력된 것들
Laptop {
calculate: [Function: calculate],
osType: 'macOs',
wegiht: '1.7kg'
}
Laptop {
calculate: [Function: calculate],
osType: 'windowOs',
wegiht: '1kg'
}
macOs
1kg
macOs, 1.7kg
Static(정적 메서드)
class내부에 작성할 수 있는 메서드는 3가지이다. (생성자메서드, 프로토타입 메서드, 정적 메서드) 이것들중 static은 정적 메서드이다.
클래스를 통해 생성되는 프로퍼티와 메서드를 인스턴스레벨의 프로퍼티와 메소드라고 한다.
모든 인스턴스가 동일하게 참조해야할 프로퍼티나 메소드가 있다면 instance레벨의 프로퍼티와 메소드가 아닌, class레벨의 프로퍼티와 메소드를 사용하면 된다.
class레벨의 프로퍼티와 메소드 -> static은 인스턴스에 포함되지않고 class에 남아있게 되고 재사용할 수 있다.
만약 클래스내의 static이란 키워드를 붙이지 않는다면, 인스턴스레벨이기때문에 클래스이름자체로는 접근이 안된다.
class Laptop {
// static 정적 프로퍼티, 메서드
static MAX_Laptop = 4;
constructor(osType, osVersion) {
this.osType= osType;
this.osVersion= osVersion;
}
// 클래스 레벨의 메서드
// -> 클래스별로 공통적으로 사용할 수 있고, 인스턴스의 데이터를 참조할 필요가 없는 메서드나 프로퍼티는 클래스레벨로 작성한다.
static makeRandomLaptop() {
// 클래스 레벨의 메서드에서는 this를 참조할 수 없음(클래스 자체로는 데이터가 채워지지않은 템플릿 상태이므로)
return new Laptop('linux', '6.0');
}
// (static이 붙지않은)인스턴스레벨의 프로퍼티와 메소드는 클래스네임으로 접근이 불가
// console.log(Laptop. osType);
// Laptop.calculate();
// TypeError: Laptop.calculate is not a function
// 인스턴스 레벨의 메서드 -> 인스턴스의 데이터를 참조할 필요가 있는 메서드나 프로퍼티는 인스턴스레벨로 작성한다.
calculate = () => {
console.log(`${this.osType}: ${this.osVersion}`);
};
}
// 클래스레벨의 메서드는 만들어진 인스턴스에서 호출하는 것이 아니라, 직접호출이 가능하다.
const linux = Laptop.makeRandomLaptop();
console.log(linux);
console.log(Laptop.MAX_FRUITS);
// apple은 Fruit 클래스의 인스턴스이다.
const macBook = new Laptop('macOs', '13.0');
// Laptop { calculate: [Function: calculate], osType: 'macOs', osVersion: '13.0' }
// -> 클래스가 인스턴스에 들어있지 않다는 것을 확인할 수 있다.
// orange은 Fruit 클래스의 인스턴스이다.
const gram = new Laptop('windowOs', '11.x');
console.log(macBook);
console.log(gram);
console.log(macBook.osType);
console.log(gram.osVersion);
macBook.calculate();
// built-in-object파트에서 배우게 될 static
// Math라는 class가 있는데 거기서 static을 호출하는 것이다.
Math.pow();
// Number라는 class가 있는데 거기서 static을 호출하는 것이다.
Number.isFinite(1);
Field
// 접근제어자를 토앻 캡슐화할 수 있다. (외부에서 보이지않도록, 수정할 수 없도록)
// private(#), public(기본), protected
class Laptop {
// osType; (생략 가능한 부분)
// osVersion; (생략 가능한 부분)
#osType;
#osVersion;
#type = "노트북";
constructor(osType, osVersion) {
this.#osType = osType;
this.#osVersion = osVersion;
}
#calculate = () => {
console.log(`${this.#osType}: ${this.#osVersion}`);
};
}
const macBook = new Laptop("macOs", "13.0");
//macBook.#osType = 'window'; // #field는 외부에서 접근이 불가능함
console.log(macBook);
Getter&Setter
// Getter와 Setter를 접근자 프로퍼티(Accessor Property)라 한다.
// 변수처럼 보이긴하지만 실제로는 함수이다.
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.lastName}${this.firstName}`;
}
set fullName(value) {
console.log("set", value);
}
}
const student = new Student("수지", "김");
student.firstName = "안나";
console.log(student.firstName);
console.log(student.fullName);
student.fullName = "김철수";
객체의 프로퍼티는 크게 두 종료류 나눌 수 있다. 첫째는, 데이터 프로퍼티 둘째는, 접근자 프로퍼티이다.
접근자 프로퍼티의 본질은 함수인데, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당한다. 그런데 외부코드에서는 함수가 아닌 일반적인 프로퍼티(데이터 프로퍼티)처럼 보인다.
접근자 프로퍼티는 'getter(획득자)'와 ‘setter(설정자)’ 메서드로 표현된다. 객체 리터럴 안에서 getter와 setter 메서드는 get과 set으로 나타낼 수 있다.
getter 메서드는 obj.propName처럼 프로퍼티를 읽으려고 할 때 실행되고 이때 함수를 호출하는 것처럼 접근 하는 것이 아니라,
console.log(student.fullname()) (x) ->console.log(student.fullname) (o)와 같이 일반 프로퍼티에 접근하는 것처럼 동작할 수 있다.
setter 메서드는 obj.propNAme = value처럼 프로퍼티에 값을 할당하려 할 때 실행된다.
Extends(클래스 확장)
클래스를 extends하는 방법은 다음과 같다.
1. 부모 class 생성
class Animal {
constructor(color) {
this.color = color;
}
eat() {
console.log('먹자!');
}
sleep() {
console.log('잔다');
}
}
2. 자식 class에서 extends 예제1)
class Tiger extends Animal {}
const tiger = new Tiger('노랑이');
console.log(tiger);
tiger.sleep();
tiger.eat();
3. 자식 class에서 extends 예제2)
class Dog extends Animal {
play() {
console.log('놀자아~!');
}
const dog = new Dog("빨강이");
console.log(dog);
dog.sleep();
dog.eat();
dog.play();
4. 3. 자식 class에서 extends 예제3) - 오버라이딩(overriding)/수퍼(super)키워드
class Dog extends Animal {
constructor(color, ownerName) {
super(color); // 상속받고있는 부모생성자를 '선택'해, 인자 받아올때 super키워드 사용
this.ownerName = ownerName;
}
play() {
console.log('놀자아~!');
}
// 부모class의 method를 자식class의 method에서 덮어씌울때에는, 오버라이딩 overriding
eat() {
super.eat();
console.log('강아지가 먹는다!');
}
}
const dog = new Dog('빨강이', '엘리');
console.log(dog);
dog.sleep();
dog.eat();
dog.play();
이때 상속된기능을 유지한 채 새로운 기능을 추가할지, 상속된기능에 새로운 기능을 덮어씌우기를 할지 선택할 수 있다.
// 부모class의 기능을 그대로 유지한 채 자식class의 행동도 추가!
eat() {
super.eat();
console.log('강아지가 먹는다!');
}
// 자식class의 행동을 부모class의 기능에 덮어씌우기!
eat() {
console.log('강아지가 먹는다!');
}