Javascript의 클래스

이정수·2024년 10월 20일
0

클래스

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/class

  • 객체를 생성할 수 잇는 템플릿(청사진, 틀)
  • 클래스를 이용해서 만든 객체를 인스턴스(instance)라고 한다.
class Fruit {
  //생성자 : 필요한 데이터를 인자로 받아서 채워줌, new 키워드로 객체를 생설할 때 호출되는 함수
  constructor(name, emoji) {
    this.name = name
    this.emoji = emoji
  }
  //function 키워드를 쓸수 없다.
  display = () => {
    console.log(`${this.name} : ${this.emoji}`)
  }
}
//클래스 생성하고 사용하기
const apple = new Fruit('apple', '🍎')
console.log(apple)
apple.display()
  • apple이라는 객체는 Fruit 클래스의 인스턴스이다.

static 재사용성 높이기 : 클래스 레벨의 메소드와 인스턴스

위에서 만든 Fruit클래스 안의 name, emoji, display메소드인스턴스 레벨의 프로퍼티와 메소드라고 한다

→ 인스턴스레벨의 속성과 메소드는 반드시 생성된 인스턴스를 통해 접근해야 한다.

  • 인스턴스에 프로퍼티와 함수들이 중복으로 만들어진다.

만약 모든 객체 마다 동일하게 참조하는 속성이나 행동은 클래스 레벨의 프로퍼티와 메소드로 만든다.

→ 클래스에 한번만 정의하고, 재사용할 수 있다.

static 키워드를 통해 클래스레벨의 프로퍼티와 메서드를 만들 수 있다.

class Fruit {
  constructor(name, emoji) {
    this.name = name
    this.emoji = emoji
  }
  display = () => { //만들어진 객체에 주어진 데이터에 접근해야 한다. -> 인스턴스레벨로 둔다.
    console.log(`${this.name} : ${this.emoji}`)
  }

  //클래스별로 공통적으로 사용할수 있고
  // 만들어진 인스턴스의 데이터에 접근할 필요가 없다. -> 클래스레벨로 둔다.
  static makeRandomFruit(){
    //클래스 레벨의 메서드는 this를 참조할 수 없다.
    return new Fruit('banana','🍌')
  }
  static MAX_FRUITS = 4
}
console.log(Fruit.makeRandomFruit())
Fruits.MAX_FRUITS
  • 🤔 만들어진 객체에 주어진 데이터에 접근해야 한다. → 인스턴스레벨로 둔다.
  • 🤔 클래스 별로 공통적으로 사용할 수 있고, 만들어진 인스턴트의 데이터에 접근할 필요 없다클래스 레벨로 둔다.
  • 클래스 레벨의 메서드는 this 를 참조할 수 없다.
  • 클래스 레벨의 프로퍼티/메서드는 인스턴스안에 들어있지 않다.
    • 클래스명 . 으로만 접근이 가능하다.
  • 클래스레벨의 메서드 예시
    • Math.pow()
    • Number.isFinite() 등등

필드 / 접근제어자

클래스에서는 접근제어자를 이용한 캡슐화를 통해 외부에서 특정 필드에대한 접근가능여부를 결정할 수 있다.

  • private ( # ) : 외부에서 접근 불가능
  • public (기본값) : 외부에서 볼 수 있고 접근 가능
  • protected : 외부에서는 볼 수 없지만, 상속된 자식 클래스에서만 접근 가능

# 키워드를 통해 name과 emoji필드를 캡슐화해준다.

class Fruit {
  #name //내부에서는 사용가능, 외부에서 접근불가능
  #emoji
  type = '과일'
  constructor(name, emoji) {
    this.#name = name
    this.#emoji = emoji
  }
    display = () => {
    console.log(`${this.name} : ${this.emoji}`)
  }
}

콘솔에 Fruit의 인스턴스인 apple을 찍어보면

console.log(apple)
Fruit { type: '과일', display: [Function: display] }

→ 이런식으로 #으로 캡슐화된 name, emoji 필드에 대한 정보는 감춰진다.

게터와 세터

새로운 예시로 학생 클래스를 만들어보자

class Student {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

여기서 학생의 이름+성을 합친 fullName이라는 새로운 프로퍼티를 만드려면 어떻게 해야할까?

  1. 생성자에 this.fullName 을 만든다. ⇒ 결론 ❌
class Student {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.fullName = `${this.firstName}${this.lastName}`
  }
 }

이 방법은 직관적으로는 가능할것 같은데..실제로 이렇게 구현하면 안된다.

그 이유는, 클래스가 생성되고 나서 → 즉, 생성자 함수가 실행되고 나면 fullName이 지정된 상태가 된다.

이후 firstName을 ‘수지’에서 ‘안나’로 변경하더라도 fullName은 여전히 ‘수지킴’이다.

  1. fullName을 새로 만들어서 반환하는 함수를 만든다. ⇒ 결론 ❌

그렇다면 함수를 호출하는 시점에 fullName이 만들어질 수 있도록 fullName을 만들어서 반환하는 함수를 만든다.

class Student {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  **fullName() {
    return `${this.firstName}${this.lastName}`
  }**
}
...(클래스생성)

student.fullName()

뭔가 이상하다..!

firstName, lastName은 프로퍼티에 접근하듯이 할수있는데 → student.firstName

fullName은 함수를 호출 시켜서 접근해야 한다. → student.fullName**()**

fullName은 행동이 아니라 상태를 얻기 위한 것이므로 함수로써 정의하는 것은 무언가 이상한 느낌이 든다.

그렇다면 이 문제는 어떻게 해결할까..?

접근자 프로퍼티(get, set)

앞서 작성해둔 fullName() 함수앞에 get키워드를 붙여서 만들수 잇다.

  • 이것은 함수이므로 (constructor처럼) 고정된 값이 아니라 호출시점에 데이터를 만들어서 리턴
  • 함수처럼 특정 행동이 아니라 속성의 한부분으로 간주되는 것을 만들어야 할때 사용
  • 함수호출방식이 아닌 프로퍼티에 접근하는 것처럼 할 수 있다.
class Student {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
  **get** fullName() {
    //함수지만 고정된 값이 아니라 호출시점에 데이터를 만들어서 리턴한다.
    // 이는 속성에 가깝기 때문에 속성의 한부분으로 간주되는 것을 만들어야 할때
    // 함수호출방식이 아닌 프로퍼티에 접근하는 것처럼 할 수 있다.
    return `${this.firstName}${this.lastName}`
  }
}
  • 프로퍼티에 접근하듯이 .fullName 으로 사용할 수 있고,
  • 내부적으로는 호출시점에 데이터를 만들어서 반환하기 때문에 업데이트도 잘 이루어진것을 확인할 수 있다.

상속

class Animal {
  constructor(color) {
    this.color = color
  }
  eat() {
    console.log('먹자')
  }
  sleep() {
    console.log('잔다')
  }
}

먼저 부모 클래스인 Animal을 만든다

class Tiger extends Animal {}

const tiger = new Tiger('노랑이')
console.log(tiger)
tiger.sleep()
tiger.eat()

extends 키워드를 사용하여 이 부모클래스를 상속한 Tiger클래스를 만들 수 있다.

tiger는 부모클래스에서 만들어진 프로퍼티와 메서드를 모두 사용할 수 있다.

상속하고 메서드, 프로퍼티 추가하기

class Dog extends Animal {
  constructor(color, ownerName) {
    super(color) //부모생성자를 호출해서 필요한 정보를 전달해줘야 한다.
    this.ownerName = ownerName //dog 클래스에만 있는 프로퍼티
  }
  play() {
    console.log('놀자!')
  }
  eat() {
    //부모의 행동을 덮어씌움 -> 오버라이딩
    console.log('강쥐가 먹는다.')
  }
  sleep() {
    super.eat() //부모의 eat함수를 호출하고
    console.log('강아지는 냠냠') //자식만의 고유한 기능을 추가할 수 있다.
  }
}

Dog 클래스는 마찬가지로 Animal클래스를 상속한 자식 클래스이다.

  • Dog 클래스에 추가로 주인이름을 나타내는 ownerName 이라는 프로퍼티를 추가하려면
    • super() 를 통해 부모클래스에서 필요한 데이터들을 먼저 전달해주어야 한다.
    • 이후 owenerName프로퍼티를 가질 수 있다.
  • Dog 클래스에서만 사용가능한 play()와 같은 메소드를 추가할 수도 있다.
  • 부모 클래스에도 존재하는 eat()메소드를 자식클래스에서 정의하면 자식클래스의 메소드가 덮어씌워져 적용된다.
  • 만약 부모클래스의 메소드를 호출하고 → 자식 메소드를 추가로 넣으려면 super.eat() 처럼 부모클래스의 메소드를 호출할 수 있다.
profile
keep on pushing

0개의 댓글