[DAY14] class 가 다른 class

Jhey·2024년 10월 31일
0

JavsScript

목록 보기
9/18

Class

프로토타입을 쉽게 사용하기 위한 sugar syntax로, 생성자 함수와 내부 구조는 같지만 선언하는 문법이 다르다. 어떻게 보면 생성자 함수를 좀 더 유연하게 작성할 수 있는 문법이 아닐까?


0. 구조

0.1 Class와 생성자 함수 구조 비교

  • Class 구조
    class Person {
        getInfo() {}
    }
    const person = new Person(); 
    person.getInfo();
  • 생성자 함수 구조
    function Person() {
        this.getInfo = function() {}
    }
    const person = new Person(); 
    person.getInfo();
    Class와 생성자 함수의 구조를 비교해 보면, 인스턴스를 생성하는 방식은 같지만 문법적으로 차이가 있다. 언뜻 보기에도 Class가 더 간결하고 직관적인 문법을 제공한다.

0.2 Class 구조 자세히 보기

class Person {
  // Person 객체의 인스턴스가 생성되었을 때 한 번 실행됨
  constructor(name, age) { // 매개변수를 받아주기 위해 선언하는 함수
  }
	
  getInfo() { // 멤버 속성에 들어가 있지 않고 처음부터 프로토타입에 들어 있음
    console.log();
  }
}

const personC = new Person("영희", 30);

➡️ class에서 메서드를 만들면 자동으로 프로토타입에 추가되어 메모리를 효율적으로 사용하게 됩니다.



1. 상속

Class를 통한 상속이란 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받아 사용하는 것을 뜻한다.

class Person {
  constructor(name, age) {}
  greet() {}
}

class Employee extends Person {
  constructor(name, age, position) {
    super(); // 부모의 name과 age를 사용하겠다.
    this.name = name;
    this.age = age;
    this.position = position;
  }
}

1️⃣ 상속받을 classextends를 사용한다.

2️⃣ super를 사용하여 부모의 constructor를 참조한다.

3️⃣ 부모의 greet() 메서드에 접근할 수 있게 된다.

1.1 extends 와 프로토타입

출처:javascript.info

➡️ Rabbit이 Animal class를 상속받았을때 위와 같은 흐름이 실행된다.

1️⃣ extends 를 사용하면 Rabbit(자식) 의 프로토타입이 Animal(부모) 의 프로토 타입을 접근할 수 있다.

2️⃣ 객체 Rabbit에 원하는 메서드가 없습니다. 그럼 Rabbit의 프로토타입을 확인합니다.

3️⃣ Rabbit의 프로토타입에도 원하는 메서드가 없다면 extends 로 관계가 맺어진 Animal의 프로토타입에 있는지 확인합니다



2. 정적 메서드

정적 메서드를 사용하면 인스턴스를 생성하지 않고 메서드를 사용할 수 있다.

class Math {
  static Pi = 3;
  static add(n1, n2) { // static
    return n1 + n2;
  }
}
// const math = new Math(); 생성하면 오류 남
const sum = Math.add(10, 20);
console.log(Math.Pi);

1️⃣ 정적으로 지정하고 싶은 요소에 static을 작성한다.

2️⃣ 인스턴스를 생성하지 않고 바로 호출해 변수에 할당한다.



2.1 extends 와 정적 메서드

출처:javascript.info

➡️ Animal class의 정적 메소드를 Rabbit이 상속받았을때 위와 같은 흐름이 실행된다.

➡️ 정적메서드 에서의 상속

  • 일반 메서드는 부모 클래스의 프로토타입 -> 자식 클래스의 프로토타입 순으로 연결됩니다.

  • 정적 메서드는 부모 클래스 -> 자식 클래스 순으로 상속됩니다.

class Animal {}
class Rabbit extends Animal {}

// 정적 메서드
alert(Rabbit.__proto__ === Animal); // true

// 일반 메서드
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
  • 정적 메서드는 자식 클래스의 __proto__ 를 부모 클래스에 연결 한다.



2.2 ❓언제 정적 메서드를 사용할까

➡️ 데이터 형식 변환, 계산 등 특정 객체와 상관없는 메서드를 추가할 때, 특정 인스턴스와 무관한 공통 기능을 구현할 때 사용하기 좋다.

class MathUtil {
  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtil.add(5, 10)); // 15

➡️ 공유 값이나 설정이 필요한 경우, 모든 인스턴스가 공통으로 사용할 수 있는 기본 설정 값이나 규칙을 정의할 때 유용하다.

class AppConfig {
  static defaultColor = "blue";
}

console.log(AppConfig.defaultColor); // "blue"



2.3 정적 메서드 재활용 시 문제점

static 변수를 클래스에 두고 재활용할 때는 여러 인스턴스가 공유하는 특성 때문에 관리와 충돌 위험이 발생할 수 있다.

➡️ 공유 상태로 인해 발생하는 데이터 충돌
만약 인스턴스마다 다른 값을 가질 필요가 있는데 static 변수를 사용할 경우, 의도치 않게 모든 인스턴스가 같은 값을 가지게 되어 상태 관리가 어렵거나 버그가 발생할 수 있다.

class Counter {
  // 모든 인스턴스와 공유 -> 변경 시 모든 인스턴스에 영향
  static count = 0; 

  increment() {
    Counter.count++;
  }
}

const counter1 = new Counter();
const counter2 = new Counter();

counter1.increment();
console.log(Counter.count); // 1

counter2.increment();
console.log(Counter.count); // 2 (counter1도 영향을 받음)

➡️ 인스턴스로 독립적으로 구현하여 해결하기

class Counter {
  constructor() {
    this.count = 0; // 각 인스턴스마다 독립적인 count 속성
  }

  increment() {
    this.count++;
  }

  getCount() {
    return this.count;
  }
}

const counter1 = new Counter();
counter1.increment();
console.log(counter1.getCount()); // 1

const counter2 = new Counter();
counter2.increment();
console.log(counter1.getCount()); // 1, 다른 인스턴스에 영향 없음
console.log(counter2.getCount()); // 1, 각 인스턴스가 독립적임


3. 접근 제어자

접근자 프로퍼티의 본질은 함수로, get은 값을 획득하고, set은 설정하는 역할을 담당한다.

  • 인스턴스 객체의 속성 값을 바꿀 때 제어하고 싶을 경우
    • set을 사용
  • 멤버 변수를 출력하고 싶을 때
    • get을 사용
class Car {
  constructor(speed) {
    this._speed = speed;
  }
  /*
    아래 방법의 문제점:
    오류 - Maximum call stack size exceeded -> 콜스택 사이즈 초과
    이미 값이 -여서 set 값이 계속 바뀌려 해 오류 발생
    해결법:
      1. 초기값을 다른 이름으로 지정 (speed -> _speed)
  */
  set speed(value) {
    this.speed = value < 0 ? 0 : value;
  }
	
  get speed() {
    return this._speed;
  }
}

const car = new Car(200);
car.speed = -100;

console.log(car._speed);
/*
  speed를 호출하기 위해 get 함수를 생성한다.
*/
console.log(car.speed);


4. 프라이빗 필드

프라이빗으로 값을 조작하면 외부에서 변경할 수 없다.

// 인스턴스로 speed를 변경할 수 없음
#speed = 0;


5. 오버라이딩

메서드를 재정의하는 현상이다.

class Animal {
  sound() {
    console.log("sound!");
  }
}
class Dog extends Animal {
  run() {
    console.log("run");
  }
  sound() {
    console.log("dog sound");
  }
}
const dog = new Dog();
dog.run();
dog.sound(); // 오버라이딩에 의해 sound가 재정의되어 실행됨

배운 점

Class는 중요할수록 마지막에 등장하는 개념인데, 그만큼 또 어렵다. 생성자 함수와 비교하며 얼마나 유연하게 동작하는지 아는 게 중요하며, 사용법을 익히려면 많이 문제를 찾아서 풀어봐야겠다.

profile
천천히 가더라도 즐겁게 ☁

0개의 댓글

관련 채용 정보