✅ 클래스란?
사전에 정의된 속성 및 메소드들을 이용해 객체를 생성하기 위한 청사진과 같다.
ES2015에서 도입되었으며 생성자의 기능을 대체한다. class 표현식을 사용하면, 생성자와 같은 기능을 하는 함수를 훨씬 더 깔끔한 문법으로 정의할 수 있다. 클래스 이름은 생성자 함수와 마찬가지로 파스칼 케이스를 사용하는 것이 일반적이다.
// 클래스 선언문
class Person {}
// 익명 클래스 표현식
const Person = class {};
// 기명 클래스 표현식
const Person = class MyClass {};
일반적이진 않지만 함수와 마찬가지로 표현식으로 클래스를 정의할 수도 있다. 표현식으로 정의할 수 있다는 것은 클래스가 값으로 사용할 수 있는 일급 객체라는 것을 의미한다. 클래스는 함수인 것이다.
🟢 클래스 호이스팅
함수 선언과 클래스 선언의 중요한 차이점은 함수의 경우 정의하기 하기 전에 호출할 수 있지만, 클래스는 반드시 정의한 뒤에 사용할 수 있다. 클래스는 var 키워드로 선언한 변수처럼 호이스팅되지 않고 let, const 키워드로 선언한 변수처럼 호이스팅된다. 따라서 클래스 선언문 이전에 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.
클래스 몸체에서 정의할 수 있는 메서드로 constructor(생성자), 프로토타입 메서드, 정적 메서드가 있다.
// 클래스 선언문
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name; // name 프로퍼티는 public
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
// 정적 메서드
static sayHello() {
console.log('Hello!')
}
}
// 인스턴스 생성
const me = new Person('Lee');
console.log(me.name); // Lee
me.sayHi(); // Hi! My name is Lee
Person.sayHello(); // Hello!
인스턴스 생성을 위해 "new" 키워드를 사용했다. new 연산자와 함께 호출한 Person은 클래스의 이름이 아니라 constructor(생성자)이다. 표현식이 아닌 선언식으로 정의한 클래스의 이름은 constructor와 동일하다.
class Student {
constructor(firstName, lastName, year) {
this.firstName = firstName;
this.lastName = lastName;
this.grade = year;
}
}
let firstStudent = new Student("김", "정민", 3);
let secondStuent = new Student("Kim", "JeongMin");
constructor 내부에서 사용되는 경우의 "this"는 개별 클래스 인스턴스, 즉 개별 "Student" 인스턴스를 지칭하게 된다. 어떤 것에 대해 논하고 있느냐에 따라 위와 같은 경우 "firstStudent", "secondStudent"를 지칭하게 된다. 쉽게 설명하자면, 만일 Array로부터 pop 인스턴스를 수행하려면 pop 인스턴스를 실행할 개별 Array 인스턴스가 있어야 한다.
constructor
constructor는 인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수한 메소드이다.
클래스 필드(class field)란?
클래스 내부의 캡슐화된 변수를 말한다. 데이터 멤버 또는 멤버 변수라고도 부른다. 클래스 필드는 인스턴스의 프로퍼티 또는 정적 프로퍼티가 될 수 있다. 쉽게 말해, 자바스크립트의 생성자 함수에서 this에 추가한 프로퍼티를 클래스 기반 객체지향 언어에서는 클래스 필드라고 부른다.
// 클래스 선언문
class Person {
// constructor(생성자). 이름을 바꿀 수 없다.
constructor(name) {
// this는 클래스가 생성할 인스턴스를 가리킨다.
// _name은 클래스 필드이다.
this._name = name;
}
}
// 인스턴스 생성
const me = new Person('Lee');
console.log(me); // Person {_name: "Lee"}
인스턴스를 생성할 때 new 연산자와 함께 호출한 것이 바로 constructor이며 constructor의 파라미터에 전달한 값은 클래스 필드에 할당한다.
constructor는 생략할 수 있다. constructor를 생략하면 클래스에 constructor() {}를 포함한 것과 동일하게 동작한다. 즉, 빈 객체를 생성한다. 따라서 인스턴스에 프로퍼티를 추가하려면 인스턴스를 생성한 이후, 프로퍼티를 동적으로 추가해야 한다. constructor는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행하기에 클래스 필드를 초기화해야 한다면 constructor를 생략해서는 안된다.
메서드 정의하기
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.tardies = 0;
this.scores = [];
}
markLate() {
this.tardies += 1;
if(this.tardies >=3 ) {
return '너는 퇴학이야'
}
return `${this.lastName}는 ${this.tardies}회 끄였습니다.`
}
addScore(score) {
this.scores.push(score);
return this.scores
}
calculateAverage() {
let sum = this.scores.reduce((a,b) => a+b);
return sum/this.scores.length;
}
}
let JM = new Student('kim', 'jeongmin')
인스턴스로 생성한 JM이 메소드를 사용할 수 있다. markLate()를 호출하면 tardies가 1씩 증가할 것이고 3이상이 되면 "너는 퇴학이야"를 리턴할 것이다. JM.addScore(90)를 실행하면 scores배열에 인자가 들어간다. calculateAverage()를 실행하면 배열을 다 더해서 평균을 구할 것이다.
const methodName = 'introduce';
class Student {
constructor({name, age}) {
this.name = name;
this.age = age;
}
// 아래 메소드의 이름은 `introduce`가 된다.
[methodName]() {
return `안녕하세요, 제 이름은 ${this.name}입니다.`;
}
}
console.log(new Person({name: '김정민', age: 23}).introduce());
// 안녕하세요, 제 이름은 김정민입니다.
객체 리터럴의 문법과 마찬가지로, 임의의 표현식을 대괄호로 둘러싸서 메소드의 이름으로 사용할 수도 있다.
예시 살펴보기.
abstract class Shape {
public static MIN_BORDER_WIDTH = 0;
public static MAX_BORDER_WIDTH = 30;
public readonly name: string = "Shape";
protected _borderWidth: number;
private action!: string;
constructor(borderWidth: number = 0) {
this._borderWidth = borderWidth;
}
// 이 클래스를 상속 받는 하위 클래스, 자식 클래스한테 반드시 실체적인 코드를 구현하라고 지시하는 역할을 함.
abstract area: () => number;
set borderWidth(width: number) {
if (width >= Shape.MIN_BORDER_WIDTH && width <= Shape.MAX_BORDER_WIDTH) {
this._borderWidth = width;
} else {
throw new Error("borderWidth 허용 범위를 벗어난 동작을 시도했습니다.");
}
}
get borderWidth(): number {
return this._borderWidth;
}
}
class Circle extends Shape {
private _radius: number;
public name: string = "Circle";
constructor(radius: number) {
super();
this._radius = radius;
}
get radius() {
return this._radius;
}
area = () => this._radius * this._radius * Math.PI;
}
class Rect extends Shape {
private _width: number;
private _height: number;
constructor(width: number, height: number) {
super();
this._width = width;
this._height = height;
}
get width() {
return this._width;
}
get height() {
return this._height;
}
area = () => this._width * this._height;
}
const circle = new Circle(50);
const rect = new Rect(150, 200);
console.log(rect.borderWidth); // 0
console.log(rect.name); // Shape
console.log(circle.name); // Circle
try {
rect.borderWidth = 100;
} catch (error) {
console.error(error); // Error: borderWidth 허용 범위를 벗어난 동작을 시도했습니다.
}
'개발자의 공부 > JS' 카테고리의 다른 글
[JS문법] 배열1 (0) | 2023.10.04 |
---|---|
[JS] 객체지향 프로그래핑 (0) | 2023.06.02 |
[JS문법] 일급 객체 (0) | 2022.12.26 |
[JS]생성자 함수에 의한 객체 생성 (0) | 2022.12.19 |
[JS]얕은 복사와 깊은 복사 (0) | 2022.09.29 |