Front/Javascript

클래스(class) - javascript 기본

oodada 2023. 10. 5. 14:35
반응형

클래스 (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 상속

- 상속이란?

우리가 스타벅스에 가면 다양한 커피를 볼 수 있어요. 아메리카노, 라떼, 카푸치노 등등. 이 모든 커피에는 기본적으로 이름이랑 가격이라는 공통된 정보가 있죠. 이런 공통적인 부분을 하나의 큰 틀로 생각해볼 수 있어요. 이 틀을 '커피의 기본 정보'라고 한다면, 이 정보를 담는 큰 상자가 하나 있다고 상상해보세요. 이 상자의 이름을 'OrderCoffee'라고 지어볼게요.

그런데 스타벅스에는 그냥 커피 말고도 특별한 커피들이 있잖아요? 예를 들어, 한정판 커피나, 멤버십 고객을 위한 스페셜 커피 같은 거요. 이런 특별한 커피들도 기본적으로는 이름과 가격이라는 정보를 가지고 있지만, 뭔가 더 추가된 특별한 요소가 있어요.

이제 'OrderCoffee' 상자를 상속받아서 또 다른 상자를 만들어보는 거예요. 이 상자는 특별한 커피들만의 정보를 담을 수 있게 더 많은 공간이 있어요. 이 새로운 상자의 이름을 'Special'이라고 해볼게요. 'Special' 상자는 'OrderCoffee' 상자의 모든 것을 가지고 있으면서도, 그 위에 더 많은 정보를 담을 수 있는 공간이 있어요. 예를 들어, '이 커피는 한정판이에요', '이 커피를 마시면 포인트가 쌓여요' 같은 특별한 정보들 말이죠.

이렇게 'extends'를 사용해서 클래스를 상속 받는 건, 실생활에서 우리가 보는 다양한 커피들처럼, 기본적인 특성을 공유하면서도 각자의 특별한 맛과 특성을 가진 커피들을 만들어내기 위함이에요. 이렇게 하면, 기본적인 정보는 재사용하면서도, 각기 다른 커피들의 특별한 특성을 효율적으로 추가할 수 있답니다.

  • class 문법에서는 extends 키워드를 사용하여 상속을 받을 수 있다.
  • extends 키워드를 사용하여 상속을 받으면 부모 클래스의 프로토타입을 상속받는다.

- 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
반응형
티스토리 친구하기