클래스(Class)
클래스란 생성자함수와 마찬가지로 동일한 부류의 속성과 행동을 가지고있는 객체를 생성할 수 있는 템플릿(청사진, 틀)이다.
클래스를 통해 객체지향 프로그래밍(Object-Oriented Programming)이 가능하다.
자바스크립트는 프로토타입(prototype)을 베이스로한 객체지향언어이다. 따라서 이미 생성자함수를 통해 객체를 찍어내는 행위가 가능했다. 자바스크립트에서 class는 ES6이전 존재하지않았지만, class를 사용하는 여타 객체지향 언어(JAVA, Python, C++…) 에 익숙한 사람들의 수요에 의해 ES6부터 새롭게 적용 되었다. class가 도입되었음에도 자바스크립트가 프로토타입 중심 임에는 변함 없고, class 역시도 프로토타입을 기반으로 만들어진 것이다. (syntaticsugar…) 실제로도 자바스크립트로 설정한 class에 typeof() 를 입혀보면 실은 function 임을 알 수 있다.
현대 개발시장에서 객체지향프로그래밍은 생성자함수보다는 class로 쓰인다.
*클래스 혹은 생성자함수를 통해 만들어진 객체를 인스턴스(Instance)라 한다.
*함수이지만 객체내에 작성된 함수를 메서드(method)라 한다.
*최초로 값을 저장하는 행위를 "변수를 초기화한다." 라고 표현한다.
Class 사용법
class Laptop {
constructor(osType, wegiht) {
this.osType= osType;
this.wegiht= wegiht;
this.calculate = () => {
console.log(`${this.osType}, ${this.wegiht}`);
};
}
}
- class 작성(새성자함수를 작성할때 썻던 function대신)
- class네임의 첫글자는 생성자함수를 작성할때와 마찬가지로 대문자로 작성한다.
(생성자함수와 다르게 맨 앞글자를 소문자로 작성해도 오류가 나지는 않음) - constructor(생성자) 작성 -> new 연산자로 객체를 생성할 때 호출되는 함수이다.
- 클래스에 적혀지는 클래스의 멤버함수는 생성자범위 내에서 작성하는 것이 아니라, 생성자범위 밖에서 작성한다. (에러가 발생하지는 않지만 통상적으로 그렇게 작성한다.) 따라서 위의 코드보다 아래의 코드가 올바르게 작성된 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가지이다.
- 생성자메서드(constructer)
- 프로토타입 메서드(일반함수)
- 정적 메서드(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);
- static프로퍼티나 static메서드는 this를 참조할 수 없다. 이는 클래스레벨의 프로퍼티나 메서드는 인자를 받지않은 상태에서, 클래스네임으로 직접 호출하므로 참조할 것이 없기때문이다.
-
static프로퍼티나 static메서드는 class네임을 통해야만 직접호출이 가능하다.(만들어진 instance에서 호출하는 것이 아님)
-
static프로퍼티나 static메서드는 인스턴스에 들어있지 않다. 클래스에 남아있어, 재사용할 수 있다.
Field
자바스크립트의 field는 총 3가지이다.
- private(#)
- public(기본메서드)
- protected(외부에서 볼수는 없지만, 상속된자식 class에서만 접근이 가능하도록)
이중 private filed는 외부에서 접근할 수 없고, 노출되지도 않는다. 예를들어 어떠한 객체나 클래스내의 모든 프로퍼티나 메서드가 private filed로만 이루어져있고, 그러한 객체나 클래스를 consol.log를 이용해 호출한다면 정말 아무 내용도 보이지 않는다.
이러한 private filed는 macBook.osType = 'window'가 되는 불상사를 막아주는 식으로 활용할 수 있다.
// 접근제어자를 통해 캡슐화할 수 있다. (외부에서 보이지않도록, 수정할 수 없도록)
// private(#), public(기본메서드), protected(외부에서 볼수는 없지만, 상속된자식 class에서만 접근이 가능하도록)
class Laptop {
#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);
console.log(Laptop.type);
console.log(macBook.type);
Getter&Setter
객체의 프로퍼티는 크게 두 종료류 나눌 수 있다.
- 데이터 프로퍼티: 일반적인 프로퍼티
- 접근자 프로퍼티: Getter&Setter
접근자 프로퍼티의 본질은 함수이다. 이 함수는 이름그대로 값을 불러오고(get)하고 할당(set)하는 역할을 담당한다.
// 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 = "김철수";
- Getter-> 접근할때 함수호출, 프로퍼티에 접근하는 것처럼 괄호를 사용하지않아도 된다.
- Setter-> 할당할때 함수호출
이 게터와 세터의 효용성에 의문이 들어 생각해보고, 간단히 요약하자면
같은 메서드네임을, 접근하는 용도와 할당하는 용도로 나눌 수 있다는 것이다.
또한 게터의경우 함수를 호출하는 방식이 아니라, 프로퍼티에 접근할때와 같은 방식으로 사용할 수 있다는 것 뿐이다.
Extends(클래스 확장)
클래스를 extends하는 방법은 다음과 같다.
1. 부모 class 생성
class Animal {
constructor(color) {
this.color = color;
}
eat() {
console.log('먹자!');
}
sleep() {
console.log('잔다');
}
}
2. 자식class생성
// 자식클래스 상속받기
class Tiger extends Animal {}
// 자식클래스 생성하기
const tiger = new Tiger('노랑이');
console.log(tiger);
tiger.sleep();
tiger.eat();
3. 자식 class에서 extends 예제2)
// 자식클래스 상속받기
class Dog extends Animal {
constructor(color, ownerName) {
super(color); // 상속받고있는 부모생성자를 '선택'해, 인자 받아올때 super키워드 사용
this.ownerName = ownerName; // 자식class에서 constructer을 정의하는 순간 부모class의 인자를 다 받아온 후 추가해야한다.
}
play() {
console.log("놀자아~!");
}
eat() {
super.eat(); // 부모의 기능은 그대로 유지한채 새로운 기능을 추가
console.log("강아지가 먹는다!"); // 부모class의 method를 자식class의 method에서 덮어씌울때에는, 오버라이딩 overriding
}
}
// 자식클래스 생성하기
const dog = new Dog("빨강이");
console.log(dog);
dog.sleep();
dog.eat();
dog.play();
- super키워드를 이용해 부모의 기능을 그대로 유지한 채 새로운 기능을 추가할 수 있다.
- 만약 위의 예제에서 super키워드를 이용하지 않는다면 부모기능에 덮어씌우는 오버라이딩이 된다.
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();
- constructer에 새로운 인자를 추가하고싶다면 기존 인자를 받아오고나서 추가해야한다.
// 부모class의 기능을 그대로 유지한 채 자식class의 행동도 추가!
eat() {
super.eat();
console.log('강아지가 먹는다!');
}
// 오버라이딩(overriding):자식class의 행동을 부모class의 기능에 덮어씌우기!
eat() {
console.log('강아지가 먹는다!');
}
quiz1) 카운터 만들기
class Counter {
#value; // 외부에서 값을 변경하면 안되므로 접근제어자를 통해 제어(private filed화)해준다.
constructor(startValue) {
if (isNaN(startValue) || startValue < 0) {
this.#value = 0;
// 왜 생성자의 인자인 startValue가 아니라 value일까..?
// 생성자인자가 startvalue인데 this를 생성자인자가 아닌 곳에서도 쓰일 수 있는가?
} else {
this.#value = startValue;
}
}
get value() {
// 접근제어자를 통해 제어해준 값을 보여줘야하므로 게터를 통해 접근가능하도록 해준다.
return this.#value;
}
increment = () => {
this.#value++;
};
}
const counter = new Counter(0);
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.value);
'자바스크립트 개념정리' 카테고리의 다른 글
자료구조(data structure) (0) | 2022.11.29 |
---|---|
내장객체(Built-in Objects) (0) | 2022.11.28 |
객체(object) (0) | 2022.11.20 |
반복문(iterate)(loop) (0) | 2022.11.07 |
데이터 타입 (모던 자바스크립드 Deep Dive 6장) (0) | 2022.10.31 |