클래스 (class)
Class는 객체를 만들기 위한 템플릿이며, 프로토타입 기반 상속을 보다 명시적이고 간편하게 사용할 수 있도록 해줍니다.
- class를 사용하지 않으면, 객체를 생성하고 속성과 메서드를 추가하는 과정이 번거롭고, 중복된 코드가 많아질 수 있습니다.
- class를 통해 원하는 구조의 객체 틀을 만들고, 이를 통해 객체를 생성하고 속성과 메서드를 추가할 수 있습니다.
- 클래스 = 붕어빵 틀 / 객체 = 붕어빵
0. 클래스의 개념
"커피숍"을 운영한다고 가정하고, 커피숍에서 판매하는 다양한 종류의 커피를 클래스를 사용하여 모델링해 보겠습니다.
- 클래스를 사용하지 않은 경우
// 생성자 함수를 사용하여 커피 객체를 생성
function OrderCoffee(name, price, size) {
// 커피의 속성
this.name = name;
this.price = price;
this.size = size;
}
// 커피의 가격을 사이즈에 따라 계산하는 메서드
// 생성자함수.prototype.메서드명 = function(){} : 생성자 함수의 프로토타입에 메서드를 생성한다.
OrderCoffee.prototype.getPrice = function () {
if (this.size === 'large') {
return this.price + 500; // 큰 사이즈는 추가 가격이 붙습니다.
}
return this.price;
};
// 주문 접수 메서드
OrderCoffee.prototype.makeCoffee = function () {
return `Making a ${this.size} ${this.name}}.`;
};
// 계산 메서드
OrderCoffee.prototype.order = function () {
return `${this.name}} ${this.size} 는 ${this.price}원 입니다.`;
};
// 생성자 함수를 사용하여 각각의 커피 객체를 생성
var americano = new OrderCoffee('americano', 3000, 'small');
var latte = new OrderCoffee('Latte', 4000, 'medium');
var cappuccino = new OrderCoffee('Cappuccino', 4500, 'large');
// 메서드 호출
console.log(cappuccino.getPrice()); // 5000
console.log(americano.makeCoffee()); // "Making a small americano."
console.log(latte.order()); // "Latte medium 는 4000원 입니다."
- 문제 상황
커피숍에서는 여러 종류의 커피를 판매합니다. 각 커피는 이름, 가격, 사이즈, 재료 등의 속성을 가지고 있고, 커피를 만들고, 가격을 계산하는 등의 기능이 필요합니다. 만약 클래스를 사용하지 않는다면, 각 커피를 개별적으로 변수로 선언하고, 관련 함수를 따로 구현해야 합니다. 이는 중복된 코드가 많아지고, 관리하기 어려워질 수 있습니다.
- 클래스를 사용한 해결 방법
커피를 클래스로 정의함으로써, 각 커피 객체의 속성과 기능을 캡슐화하고 재사용할 수 있게 됩니다. 이를 통해 코드의 구조화, 조직화가 이루어지고, 새로운 커피 종류를 추가하거나 변경하기 쉬워집니다.
// 클래스를 사용한 경우
class OrderCoffee {
// 생성자 함수 역할을 하는 constructor 메서드
constructor(name, price, size) {
this.name = name;
this.price = price;
this.size = size;
}
// 커피를 만드는 메서드
makeCoffee() {
return `Making a ${this.size} ${this.name}.`;
}
// 커피의 가격을 사이즈에 따라 계산하는 메서드
getPrice() {
if (this.size === 'large') {
return this.price + 500; // 큰 사이즈는 추가 가격이 붙습니다.
} else if (this.size === 'small') {
return this.price - 500; // 작은 사이즈는 할인 가격이 적용됩니다.
} else {
return this.price;
}
}
// 계산 메서드
order() {
return `${this.name} ${this.size} 는 ${this.getPrice()}원 입니다.`;
}
}
// 클래스를 사용하여 각각의 커피 객체를 생성
const americano = new OrderCoffee('americano', 3000, 'small');
const latte = new OrderCoffee('Latte', 4000, 'medium');
const cappuccino = new OrderCoffee('Cappuccino', 4500, 'large');
// 메서드 호출
console.log(cappuccino.getPrice()); // 5000
console.log(americano.makeCoffee()); // "Making a small americano."
console.log(latte.order()); // "Latte medium 는 4000원 입니다."
1. 생성자 함수란?
클래스 함수를 사용하기 전에 생성자 함수에 대해 알아보겠습니다.
- 객체를 생성할 때 속성을 초기화하고, 메서드를 추가할 수 있도록 하는 함수입니다.
- 생성자 함수란 new 연산자를 통해 객체를 생성하는 함수를 말한다.
- 생성자 함수는 일반 함수와 구분하기 위해 첫 글자를 대문자로 표기한다.
- 생성자 함수를 통해 객체 생성
// const animals = ['dog', 'cat', 'lion', 'tiger']; // 배열 리터럴
const animals = new Array('dog', 'cat', 'lion', 'tiger');
// 생성자 함수는 new 연산자를 통해 객체를 생성하는 함수를 말한다.
console.log(animals); // 배열 전체
console.log(animals.length); // 배열의 길이
console.log(animals[0]); // 배열의 첫번째 요소
console.log(animals.includes('dog')); // includes : 배열에 특정 요소가 있는지 확인
console.log(animals.includes('bird'));
// 위와 같이 length, includes등을 프로토타입 속성이라고 한다.
2. prototype 이란?
프로토타입 객체는 새로운 객체가 생성되기 위한 원형이 되는 객체이다.
array mdn 검색 -> 아래 링크를 보면 array에 prototype이 붙어있다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array
array에 사용할 수 있는 속성 및 메소드는 프로토타입에 연결되어 있다.
우리 모두는 부모님으로부터 여러 가지 특성(키, 눈 색깔 등)을 '상속'받습니다. 우리가 직접 이런 특성을 선택하거나 만들어내지 않았지만, 자연스럽게 이런 특성을 갖게 됩니다. JavaScript의 객체도 비슷하게 동작합니다. 각각의 객체는 특정한 프로토타입(부모 객체라고 생각할 수 있음)과 연결되어 있으며, 그 프로토타입의 속성이나 메서드를 마치 자신의 것처럼 사용할 수 있습니다.
- prototype을 이용하여 메소드 생성
- length, includes등의 메소드는 이미 자바스크립트에서 만들어져 있는 속성이나 메소드이고
- 이러한 속성이나 메소드를 사용자가 직접 만들어서 사용할 수 있다.
// new Array() 생성자 함수를 통해 배열을 생성한다.
const starbucks = new Array('아메리카노', '라떼', '카푸치노');
// Array.prototype.Menu() : 배열의 프로토타입에 Menu() 메소드를 생성한다.
Array.prototype.Menu = function () {
console.log(this); // this : 배열을 가리킨다.
};
// starbucks의 배열에서 Menu() 메소드를 호출해 사용한다.
starbucks.Menu(); // ['아메리카노', '라떼', '카푸치노']
// 생성된 Menu() 메소드는 다른 배열에서도 사용할 수 있다.
const mega = ['메가리카노', '메가떼', '메가치노']; // 배열 생성
mega.Menu(); // ['메가리카노', '메가떼', '메가치노']
3. prototype을 이용한 메소드 재활용
- 메소드 중복 사용
const americano = {
name: '아메리카노',
price: 3000,
orderCoffee: function () {
// 일반 함수에서 this는 호출되는 객체를 가리킨다.
return `${this.name}는 ${this.price}원 입니다.`; // this : winter 객체를 가리킨다.
},
};
const latte = {
name: '라떼',
price: 4000,
orderCoffee: function () {
return `${this.name}는 ${this.price}원 입니다.`;
},
};
console.log(americano.orderCoffee()); // 아메리카노는 3000원 입니다.
console.latte(fall.orderCoffee()); // 라떼는 4000원 입니다.
// 같은 로직의 코드가 반복되어 비효율적이다.
- call을 이용한 메소드 재활용
const americano = {
name: '아메리카노',
price: 3000,
orderCoffee: function () {
return `${this.name}는 ${this.price}원 입니다.`;
},
};
const latte = {
name: '라떼',
price: 4000,
};
console.log(americano.orderCoffee()); // 아메리카노는 3000원 입니다.
console.log(americano.orderCoffee.call(latte)); // 라떼는 4000원 입니다.
- prototype을 이용한 메소드 재활용
- 위의 코드에서 prototype을 이용하면 조금 더 효율적으로 코드를 작성할 수 있다.
// 위에서 객체로 생성한 코드를 OrderCoffee 생성자 함수로 만든다.
function OrderCoffee(name, price) {
// 함수 내부에 this 키워드를 사용하여 객체의 속성을 생성할 수 있다.
this.name = name; // this.name 속성에 name 매개변수를 할당한다.
this.price = price;
}
// OrderCoffee 생성자 함수의 프로토타입에 order 메소드를 생성한다.
// 생성자 함수에서 화살표 함수 사용시 this가 window를 가리키기 때문에 사용할 수 없다.
OrderCoffee.prototype.order = function () {
return `${this.name}는 ${this.price}원 입니다.`;
// this : OrderCoffee 생성자 함수를 가리킨다.
};
// OrderCoffee 생성자 함수를 통해 객체를 생성한다.
// 생성자 함수란 new 연산자를 통해 객체를 생성하는 함수를 말한다.
// 생성자 함수를 호출할 때 new를 붙여서 호출한다.
const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);
console.log(americano); // OrderCoffee {name: "아메리카노", price: "3000"}
console.log(latte); // OrderCoffee {name: "라떼", price: "4000"}
console.log(americano.order()); // 아메리카노는 3000원 입니다.
console.log(latte.order()); // 라떼는 4000원 입니다.
4. es6 class 이용한 메소드 재활용
- es6에서는 class 문법을 사용하여 객체를 생성할 수 있다.
- class 문법을 사용하면 생성자 함수를 사용하지 않고도 객체를 생성할 수 있다.
- class 문법에서는 constructor() 메소드를 사용하여 객체를 생성하고 프로토타입에 메소드를 생성한다.
- class 문법 사용 방법
// OrderCoffee 클래스를 생성한다.
class OrderCoffee {
// constructor : 생성자 함수 역할을 해 객체를 생성한다.
constructor(name, price) {
this.name = name;
this.price = price;
}
// OrderCoffee 클래스의 프로토타입에 order() 메소드를 생성한다.
// 따움표로 구분하지 않는다.
order() {
return `${this.name}는 ${this.price}원 입니다.`;
}
// making
making() {
return `${this.name}를 만드는 중...`;
}
// made
made() {
return `${this.name}가 만들어졌습니다.`;
}
}
// OrderCoffee 생성자 함수를 통해 객체를 생성한다.
const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);
console.log(americano.order()); // 아메리카노는 3000원 입니다.
console.log(americano.making()); // 아메리카노를 만드는 중...
console.log(americano.made()); // 아메리카노가 만들어졌습니다.
console.log(latte.order()); // 라떼는 4000원 입니다.
console.log(latte.making()); // 라떼를 만드는 중...
console.log(latte.made()); // 라떼가 만들어졌습니다.
5. class 상속
- 클래스 상속의 개념
클래스 상속은 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 사용하는 것입니다. 이를 통해 코드 재사용성을 높이고, 유지보수를 쉽게 할 수 있습니다.
예시로 이해하기
- 기본 클래스 : Order
먼저, 스타벅스의 기본 커피 정보를 담는 클래스를 생각해봅시다.
class OrderCoffee {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
- 상속받는 클래스: Special
특별한 커피는 기본 커피의 정보를 가지고 있지만, 추가 정보도 필요합니다. 이를 위해 OrderCoffee 클래스를 상속받아 Special 클래스를 만들 수 있습니다.
class Special extends OrderCoffee {
constructor(name, price, specialFeature) {
super(name, price); // 부모 클래스의 생성자를 호출
this.specialFeature = specialFeature;
}
}
간단한 설명
- OrderCoffee는 기본 커피의 이름과 가격을 저장하는 클래스입니다.
- Special은 OrderCoffee를 상속받아 특별한 기능(specialFeature)을 추가한 클래스입니다.
- extends 키워드를 사용하여 상속받으며, super()를 통해 부모 클래스의 생성자를 호출합니다.
- class 상속 사용 방법
super 키워드를 사용하여 부모 클래스의 생성자를 호출한다.
class OrderCoffee {
constructor(name, price) {
this.name = name;
this.price = price;
}
calling() {
return `${this.name}는 ${this.price}원 입니다.`;
}
// making
making() {
return `${this.name}를 만드는 중...`;
}
// made
made() {
return `${this.name}가 만들어졌습니다.`;
}
}
class SpecialCoffee extends OrderCoffee {
constructor(name, price, size) {
super(name, price); // super 키워드를 사용하여 부모 클래스의 생성자를 호출한다.
this.size = size; // size 속성을 추가한다.
}
order() {
return `스페셜 커피 ${this.name} ${this.size} 사이즈는 ${this.price}입니다.`;
}
}
const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);
console.log(americano.calling()); // 아메리카노는 3000원 입니다.
console.log(latte.calling()); // 라떼는 4000원 입니다.
const americanoS = new SpecialCoffee('아인슈페너', 6000, 'tall');
const latteS = new SpecialCoffee('연유라떼', 5000, 'small');
console.log(americanoS.order()); // 스페셜 커피 아인슈페너 tall 사이즈는 6000입니다.
console.log(latteS.order()); // 스페셜 커피 연유라떼 small 사이즈는 5000입니다.
console.log(americanoS.making()); // 아인슈페너를 만드는 중...
console.log(americanoS.made()); // 아인슈페너가 만들어졌습니다.
7. 리액트에서 클래스 사용하기
클래스 컴포넌트는 복잡한 상태 관리와 생명주기 메서드를 활용할 때 유리하며, 특히 기존의 큰 규모 프로젝트 유지보수나 특정 라이브러리 요구사항에 따라 필요할 수 있습니다. 그러나 리액트는 최신 기능과 성능 개선을 위해 함수형 컴포넌트 사용을 권장하고 있습니다.
- 클래스 함수 사용
// src/OrderCoffee.js
import React from 'react';
class OrderCoffee extends React.Component {
constructor(props) {
// 생성자 함수에서 super(props)를 호출하여 부모 클래스의 생성자를 호출한다.
super(props);
// state 객체를 사용하여 컴포넌트의 상태를 관리한다.
this.state = {
name: props.name,
price: props.price,
};
}
calling() {
return `${this.state.name}는 ${this.state.price}원 입니다.`;
}
making() {
return `${this.state.name}를 만드는 중...`;
}
made() {
return `${this.state.name}가 만들어졌습니다.`;
}
render() {
return (
<div>
<h1>{this.calling()}</h1>
<p>{this.making()}</p>
<p>{this.made()}</p>
</div>
);
}
}
export class SpecialCoffee extends OrderCoffee {
constructor(props) {
super(props);
this.state = {
...this.state, // 부모 클래스의 state를 상속받는다.
size: props.size, // size 속성을 추가한다.
};
}
order() {
return `스페셜 커피 ${this.state.name} ${this.state.size} 사이즈는 ${this.state.price}입니다.`;
}
render() {
return (
<div>
<h1>{this.order()}</h1>
<h2>{super.calling()}</h2>
<p>{this.making()}</p>
<p>{this.made()}</p>
</div>
);
}
}
export default OrderCoffee;
// src/App.js
import React from 'react';
import OrderCoffee, { SpecialCoffee } from './components/views/coffee/OrderCoffee';
function App() {
return (
<div>
<OrderCoffee
name="아메리카노"
price={3000}
/>
<OrderCoffee
name="라떼"
price={4000}
/>
<SpecialCoffee
name="아인슈페너"
price={6000}
size="tall"
/>
<SpecialCoffee
name="연유라떼"
price={5000}
size="small"
/>
</div>
);
}
export default App;
- 함수형 컴포넌트 사용
// src/OrderCoffee.js
import React from 'react';
function OrderCoffee({ name, price }) {
const calling = () => `${name}는 ${price}원 입니다.`;
const making = () => `${name}를 만드는 중...`;
const made = () => `${name}가 만들어졌습니다.`;
return (
<div>
<h1>{calling()}</h1>
<p>{making()}</p>
<p>{made()}</p>
</div>
);
}
export function SpecialCoffee({ name, price, size }) {
const order = () => `스페셜 커피 ${name} ${size} 사이즈는 ${price}입니다.`;
return (
<div>
<h1>{order()}</h1>
<h2>{calling()}</h2>
<p>{making()}</p>
<p>{made()}</p>
</div>
);
}
export default OrderCoffee;
// src/App.js
import React from 'react';
import OrderCoffee, { SpecialCoffee } from './components/views/coffee/OrderCoffee';
function App() {
return (
<div>
<OrderCoffee
name="아메리카노"
price={3000}
/>
<OrderCoffee
name="라떼"
price={4000}
/>
<SpecialCoffee
name="아인슈페너"
price={6000}
size="tall"
/>
<SpecialCoffee
name="연유라떼"
price={5000}
size="small"
/>
</div>
);
}
export default App;
- 클래스 컴포넌트와 함수형 컴포넌트 비교
- 클래스 컴포넌트는 복잡한 상태 관리와 생명주기 메서드를 활용할 때 유리하다.
- 함수형 컴포넌트는 간단한 컴포넌트를 만들 때 유리하며, 최신 기능과 성능 개선을 위해 권장된다.
'Front > JavaScript' 카테고리의 다른 글
표준 내장 객체 - 숫자(Number), 수학(Math) (0) | 2023.10.22 |
---|---|
표준 내장 객체 - 문자열(string) - javascript 기본 (0) | 2023.10.22 |
함수(Function) - javascript 기본 (0) | 2023.09.11 |
구문(Statement) - javascript 기본 (0) | 2023.09.11 |
연산자(Operator) - javascript 기본 (0) | 2023.09.11 |