JavaScript는 객체 지향 언어이다.
그럼에도 불구하고 객체 지향 언어의 대표적인 문법인 Class 문법은 ES6(2015)가 되서야 도입되었다.
클래스 기반의 프로그래밍 개발 기법(생성자, 상속 등)을 사용한다.모든 객체 지향 언어가 가지고 있는 클래스와 인스턴스의 일반적 개념을 알아보자.
공장에서 ‘상품’을 다량으로 생산해내기 위해 ‘설계도’를 만든다.
그리고 그 설계도를 토대로 각각 특징이 있는 ‘상품’을 찍어낸다.
클래스(Class)는 학교에서 다양한 종류의 책상을 만드는 설계도와 비슷
(이 특징은 변수-또는 속성과 메서드)은 무엇인지 알 수 있다.인스턴스(Instance)는 이 설계도를 보고 만들어진 실제 상품과 비슷
이처럼 클래스(Class)는 객체를 만들기 위한 설계도라고 생각할 수 있고,
이 설계도를 바탕으로 만들어진 실제 객체들은 인스턴스(Instance)라고 할 수 있다.
class 키워드를 사용해 Class 생성//class 키워드로 Person 설계도 만들기
class Person {
// constructor는 이름을 변경할 수 없다.
constructor(name, age) {
//생성자 함수에는 객체에 꼭 필요한 속성 넣어주기 <- 이름(name)과 나이(age)
// new라는 키워드를 이용해서 인스턴스를 만들 때, 기본적으로 넣어야 하는 값들
// 여기서 말하는 this는 만들어질 인스턴스를 의미
//설계도 따라 만들 실체의 이름 = 외부에서 받아온 이름
this.name = name;
this.age = age;
}
// 다양한 메소드를 아래와 같이 정의할 수 있다.
// 내부 값에 접근하려면 this.속성 으로 한다.
sayHello() {
console.log(`안녕하세요, 제 이름은 ${this.name} 입니다.`);
}
printAge() {
console.log(`나이는 ${this.age}살 이예요.`);
}
}
//설계도를 통해 인스턴스(실제 사람 객체) 만들기
// 설계도에 근거해서 이름은 "Alice", 나이는 30인 사람 만들어 줘
// 설계도에 근거해서 이름은 "Bob", 나이는 25인 사람 만들어 줘
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);
// 만든 객체를 토대로 메서드 호출하기
person1.sayHello(); // 출력: "안녕하세요, 제 이름은 Alice 입니다."
person2.sayHello(); // 출력: "안녕하세요, 제 이름은 Alice 입니다."
person1.printAge(); // 출력: "나이는 30살 이예요."
// 만들어야 하는 대상이 많을 경우, 데이터가 배열 안에 있다면
// Array.forEach()로 새로운 인스턴스 생성할 수도 있다.
Person Class는 name과 age 속성을 가지고 있으며, sayHello, printAge 메소드를 정의new 키워드를 사용하여 Person Class의 인스턴스를 생성하고, sayHello, printAge 메소드를 호출constructor 키워드를 사용하여 정의class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`안녕하세요, 제 이름은 ${this.name} 입니다.`);
}
printAge() {
console.log(`나이는 ${this.age}살 이예요.`);
}
}
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);
person1.sayHello();
person2.sayHello();
person1.printAge();
constructor 메소드는 name과 age를 인자로 받아this.name과 this.age 속성을 초기화요구사항
- Car라는 새로운 클래스를 만들되, 처음 객체를 만들 때는 다음 네 개의 값이 필수로 입력되어야 한다.
modelNamemodelYeartypeprice- 메서드
makeNoise()메서드를 만들어 클락션을 출력해라.- 이후 자동차를 3개 정도 만들어라.
class Car {
//생성자 함수로 Car 클래스에 속성 넣기
constructor(modelName, modelYear, type, price) {
this.modelName = modelName;
this.modelYear = modelYear;
this.type = type;
this.price = price;
}
// 메서드 만들기
makeNoise() {
console.log(
`${this.modelName}: 빵!`
);
}
}
// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000);
const car2 = new Car("SM5", "1999", "g", 3000);
const car3 = new Car("QM6", "2010", "g", 4500);
// 만든 객체를 토대로 메서드 호출하기
car1.makeNoise();
car2.makeNoise();
car3.makeNoise();

// 사각형 클래스 생성
class Rectangle {
//프로퍼티 생성
constructor(height, width) {
this.height = height;
this.width = width;
}
printArea() {
console.log(`사각형의 넓이는 ${this.height * this.width} 입니다.`);
}
}
//인스턴스 생성
const rect1 = new Rectangle(10, 20); //넓이 200
const rect2 = new Rectangle(3, 10); //넓이 30
rect1.printArea();
rect2.printArea();
대부분의 객체 지향 프로그래밍 언어에서 getter와 setter에 대한 기본적인 개념이 있다.
getter와 setter이다.getter와 setter를 사용하여 Class의 속성에 접근할 수 있다.
getter는 속성 값을 반환하는 메소드setter는 속성 값을 설정하는 메소드getter와 setter를 통해, 생성한 인스턴스를 정해진 규격 안에서 자유자제로 변경할 수 있다.
class Rectangle {
constructor(height, width) {
// ✅ underscore : private(은밀하고, 감춰야 할 때)
this._height = height;
this._width = width;
}
// width를 위한 getter
get width() {
return this._width;
}
// width를 위한 setter
set width(value) {
// 검증 1 : value가 음수이면 오류!
if (value <= 0) {
//
console.log("[오류] 가로길이는 0보다 커야 합니다!");
return;
} else if (typeof value !== "number") { //또는 Number
console.log("[오류] 숫자를 넣어주세요.");
return;
}
this._width = value;
}
// height를 위한 getter
get height() {
return this._height;
}
// height를 위한 setter
set height(value) {
// 검증 1 : value가 음수이면 오류!
if (value <= 0) {
//
console.log("[오류] 세로길이는 0보다 커야 합니다!");
return;
} else if (typeof value !== "number") { //또는 Number
console.log("[오류] 숫자를 넣어주세요.");
return;
}
this._height = value;
}
// getArea : 가로 * 세로 => 넓이
getArea() {
const area = this._width * this._height;
console.log(`넓이는 => ${area}입니다.`);
}
}
// instance 생성
const rect1 = new Rectangle(10, 7);
// 메서드 호출해 넓이 구하기
rect1.getArea();
💡 this._변수명
내부 인스턴스에서 사용하는
this.변수명에는 항상 변수명 앞에underscore(_)를 붙여야 한다.
underscore(_)의 의미는 프로그래밍에서 private하다는 의미를 가진다.
예를 들어this._height는 이 인스턴스 내에서만 쓰이기 위한 변수다라는 뜻이다._를 붙이지 않으면,this.변수명에 외부에서 받아온 값을 세팅하려고 setter 함수 무한으로 호출해 버려서 무한 루프가 발생한다.- 그러면 콜스택 입장에서, 콜스택에 쌓이는 실행 컨텍스트가 무한으로 생성되어 버리기 때문에
RangeError: Maximum call stack size exceeded 오류가 뜬다.
// 클래스 연습해보기!
// [추가 요구사항]
// 1. modelName, modelYear, price, type을 가져오는 메서드
// 2. modelName, modelYear, price, type을 세팅하는 메서드
// 3. 만든 인스턴스를 통해서 마지막에 set 해서 get 하는 로직까지
class Car {
//생성자 함수로 Car 클래스에 속성 넣기
constructor(modelName, modelYear, type, price) {
this._modelName = modelName;
this._modelYear = modelYear;
this._type = type;
this._price = price;
}
// getter 메서드: this._변수명 값 반환
get modelName() {
return this._modelName;
}
get modelYear() {
return this._modelYear;
}
get type() {
return this._type;
}
get price() {
return this._price;
}
// setter 메서드: this._변수명 값에 외부 값 세팅함
set modelName(value) {
//유효성 검사
if (value.length <= 0) {
console.log("[오류] 입력되지 않았습니다.");
return;
}
if (typeof value !== "string") {
console.log("[오류] 문자를 넣어주세요.");
return;
}
//검증 완료된 경우에만 setting
this._modelName = value;
}
set modelYear(value) {
if (typeof value.length === 4) {
console.log("[오류] 입력된 년도가 4자리가 아닙니다.");
return;
}
if (typeof value !== "string") {
console.log("[오류] 문자를 넣어주세요.");
return;
}
this._modelYear = value;
}
set type(value) {
if (value.length <= 0) {
console.log("[오류] 입력되지 않았습니다.");
return;
}
if (value !== "g" && value !== "e" && value !== "d") {
console.log(
"[오류] 가솔린(g), 전기차(e), 디젤(d) 중 하나를 입력해주세요."
);
return;
}
this._type = value;
}
set price(value) {
if (typeof value !== "number") {
console.log("[오류] 숫자를 넣어주세요.");
return;
}
if (value < 1000000) {
console.log("[오류] 가격은 100만원 보다 작을 수 없습니다.");
return;
}
this._price = value;
}
// 메서드 만들기
printCar() {
console.log(
`${this._modelName}, ${this._modelYear}, ${this._type}, ${this._price}`
);
}
}
// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000);
const car2 = new Car("SM5", "1999", "g", 3000);
const car3 = new Car("QM6", "2010", "g", 4500);
// 만든 객체를 토대로 메서드 호출하기
car1.printCar();
car2.printCar();
car3.printCar();
// ✅ getter 예시
console.log(car1.modelName); //Sorento
// ✅ setter 예시
car1.modelName = "kia";
console.log(car1.modelName); //kia
car1.modelName = 100;
console.log(car1.modelName); //[오류] 문자를 넣어주세요. //kia
상속은 클래스의 기능을 유산으로 내려주는 기능으로, 부모 자식 간의 관계를 생각 할 수 있다.
Class는 상속을 통해 다른 Class의 기능을 물려받을 수 있다.
subclass 또는 derived class라고 한다.superclass 또는 base class라고 한다.클래스 끼리의 상속관계를 잘 이해하기 위해서 예시를 살펴보자.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}가 말한다.`);
}
}
const me = new Animal("euna");
me.speak(); //euna가 말한다.
//✅ 하위 클래스 <- 부모 클래스 상속 받아서 만들기
class Dog extends Animal {
//✅ 부모로 부터 상속 받은 것을 재정의(오버라이딩=덮어 쓰기) 할 수 있다.
speak() {
console.log(`${this.name}가 짖는다.`);
}
}
const yeti = new Dog("Yeti");
yeti.speak(); //Yeti가 짖는다.
상속받았다.💡 오버라이딩하여 Animal Class의 speak 메소드를 덮어쓸 수 있다.Car 클래스로부터 상속받아 전기자동차 클래스를 만들어 보자.
전기차이기 때문에 constructor 상에 type은 필요 없다.
class Car {
//생성자 함수로 Car 클래스에 속성 넣기
constructor(modelName, modelYear, type, price) {
this.modelName = modelName;
this.modelYear = modelYear;
this.type = type;
this.price = price;
}
// 메서드 만들기
makeNoise() {
console.log(`${this.modelName}: 빵!`);
}
printModelYear() {
console.log(`${this.modelName}: ${this.modelYear}년도 모델`);
}
}
// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000000);
const car2 = new Car("SM5", "1999", "g", 3000000);
const car3 = new Car("QM6", "2010", "g", 4500000);
// 만든 객체를 토대로 메서드 호출하기
car1.makeNoise();
car2.makeNoise();
car3.makeNoise();
car1.printModelYear();
car2.printModelYear();
car3.printModelYear();
console.log(car1.modelName); // Sorento
// ✅ Car를 상속받는 전기 자동차 클래스 만들기
class ElectronicCar extends Car {
constructor(modelName, modelYear, price) {
// ✅ Car에 있는 constructor를 통해 자동차를 만들기 때문에 굳이 안써도 되지만
// 재정의 필요하면 constructor 써주면 된다.
//✅ 자식클래스의 constructor가 부모클래스의 constructor와 다를 경우
// super 키워드를 사용하여
// 부모의 constructor에게 이렇게 받을 거란거 알려줘야 한다.
super(modelName, modelYear, "e", price);
// => 타입은 항상 "e"로 정해져 있다고 알리기
}
}
const eleCar1 = new ElectronicCar("테슬라", "2023", 9000); // 여기서 자식 컨스트럭터에 맞게 3개만 넣으면 된다.
console.log(eleCar1.modelName); // 테슬라
eleCar1.makeNoise(); //테슬라: 빵!
eleCar1.printModelYear(); //테슬라: 2023년도 모델
super
super 키워드는 객체 리터럴 또는 클래스의 속성에 접근하거나 슈퍼클래스의 생성자를 호출하는 데 사용된다.
// Car를 상속받는 전기 자동차 클래스 만들기
class ElectronicCar extends Car {
constructor(modelName, modelYear, price, chargeTime) {
// Car에 있는 constructor를 통해 자동차를 만들기 때문에 굳이 안써도 되지만
// 재정의 필요하면 constructor 써주면 된다.
//✅ 그리고 super 키워드를 사용하여
// 부모의 constructor에 이렇게 받을 거란거 알려줘야 한다.
super(modelName, modelYear, "e", price);
// => 타입은 항상 "e"로 정해져 있다고 알리기
// ✅ 부모한테 없는 건 아래 처럼 새로 추가
this.chargeTime = chargeTime;
}
// ✅ 충전시간 구하기
getChargeTime() {
return this.chargeTime;
}
// ✅ 충전시간 세팅하기
setChargeTime(time) {
this.chargeTime = time;
}
}
const eleCar1 = new ElectronicCar("테슬라", "2023", 9000, 60); // 여기서 자식 컨스트럭터에 맞게 3개만 넣으면 된다.
console.log(eleCar1.modelName); // 테슬라
eleCar1.makeNoise(); //테슬라: 빵!
eleCar1.printModelYear(); //테슬라: 2023년도 모델
console.log("-------------------");
// ✅ 충전 시간 출력
console.log(eleCar1.getChargeTime()); //60
// ✅ 충전 시간 변경
eleCar1.setChargeTime(20); // eleCar1.chargeTime = 20;
console.log(eleCar1.getChargeTime()); //20
근데 여기선 왜 게터 세터 안 쓴거지...?
기본적으로 클래스는 객체를 다량으로 안전하고 정확히 만들기 위해 사용한다.
굳이 인스턴스화 하지 않아도 되는, 즉 객체를 만들지 않아도 되는, 변하지 않는 메소드도 있다.
이런 메소드는 정적 메소드로써 정의하여 클래스 자체를 호출하는 방식으로 사용한다.
static 키워드를 사용하여 Class 레벨의 메소드를 정의할 수 있다.
클래스명.정적메소드명();
static이라는 말에서 알 수 있듯이, 인스턴스를 만들지 않고 사용할 수 있기 때문에 유틸리티 함수, 정적 속성인 경우 인스턴스 간에 복제할 필요가 없는 데이터(똑같은 것을 공유해서 쓸 때)를 만들 때 사용되곤 한다.
즉, 인스턴스를 만들 필요가 없을 때 사용한다고 이해하면 된다.
//클래스 자체는 일반 클래스 모양인데
//✅ 클래스 안에 든 메소드가 정적인 특징을 가진다.
class Calculator {
//정적 메소드임을 명시하기 위해 static 키워드 사용
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
//원래 클래스의 메소드에 접근하려면 클래스의 인스턴스를 만들고
//클래스인스턴스명.메소드 이런식으로 접근 하지만
//✅ 정적 메소드의 경우 그냥 Class 레벨에서 호출하면 된다.
//클래스명.정적메소드명();
console.log(Calculator.add(1, 2)); // 3
console.log(Calculator.subtract(3, 2)); // 1
Calculator Class는 add와 subtract 메소드를 정의한다.