Front-end/JS

[JS] 자바스크립트에서 Class???

kdc9050 2024. 11. 8. 17:29

클래스

클래스는 객체 지향 프로그래밍에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀로, 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다.

클래스는 문법적 설탕(syntactic sugar)이라고도 불리며, 내부적으로는 프로토타입 기반으로 동작하기 때문, 이는 자바스크립트의 유연성을 유지하면서도, 개발자가 보다 쉽게 접근할 수 있도록 도움!

Syntatic Sugar란?

  • 프로그래밍 언어 차원에서 제공되는 논리적으로 간결하게 표현하는 것
  • 중복되는 로직을 간결하게 표현하기 위해 나타나게 됨

원문

Syntactic sugar, or syntax sugar, is a visually or logically-appealing “shortcut” provided by the language, which reduces the amount of code that must be written in some common situation.


클래스는 사실 "특별한 함수"입니다.

  • 함수를 함수 표현식과 함수 선언으로 정의할 수 있듯이 class 문법도 class 표현식, class 선언 두 가지 방법을 제공
class User {}
// 클래스가 함수라는 증거
console.log(typeof User); // function

⇒ 클래스는 일급객체로서 다음과 같은 특징을 가짐

  1. 변수에 할당이 가능해야 함
const sum = function () {};
  1. 함수의 인자로 전달이 가능해야 함
funciton greet(callback) {
    callback();
}
greet(function() {
    console.log('Hello');
}
  1. 함수의 반환 값으로 사용이 가능해야 함
function outer() {
  return function () {
    console.log("Hello");
  };
}
const inner = outer();
inner();
  1. 동적으로 생성이 가능해야 함
const dynamic = new Function("a", "b", "return a + b");
console.log(dynamic(1, 2));

Strict mode

  • 클래스의 본문(body)은 strict mode에서 실행됨. 즉, 여기에 적힌 코드는 성능 향상을 위해 더 엄격한 문법이 적용됨. 그렇지 않으면, 오류가 발생할 수 있음

Constructor (생성자)

  • 생성자는 클래스 안에 하나만 존재! → 여러 개가 존재하SyntaxError 발생
  • 부모 클래스의 생성자를 호출하려면 super() 사용

클래스 선언

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

함수 선언클래스 선언의 중요한 차이점은 함수의 경우 정의하기 하기 전에 호출할 수 있지만, 클래스는 반드시 정의한 뒤에 사용할 수 있다는 점

const p = new Rectangle(); // ReferenceError

class Rectangle {} // 클래스가 호이스팅 될 떄 초기화가 안 되기 때문임.

클래스 표현식

  • Class 표현식은 이름을 가질 수도 있고, 갖지 않을 수도 있음.이름을 가진 class 표현식의 이름은 클래스 body의 local scope에 한해 유효
// unnamed
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 출력: "Rectangle"

// named
let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 출력: "Rectangle2"

인스턴스 생성

const Person = class Human {};
const me = new Person();

클래스의 상속

  • extends 키워드를 사용하여 상속을 받을 수 있음
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // super class 생성자를 호출하여 name 매개변수 전달
  }
  speak() {
    console.log(`${this.name} barks.`);
  }
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
  • 클래스는 생성자가 없는 객체(non-constructible)를 확장할 수 없다. 만약 기존의 생성자가 없는 객체를 확장하고 싶다면, Object.setPrototypeOf() 메서드를 사용해야 함
const Animal = { //생성자가 없는 객체
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
};
class Dog {
  constructor(name) {
    this.name = name;
  }
}
// 이 작업을 수행하지 않으면 speak를 호출할 때 TypeError가 발생합니다
Object.setPrototypeOf(Dog.prototype, Animal); 
// Object.setPrototypeOf 메서드를 통해서 Animal을 Dog 클래스에 확장

let d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Private 필드

  • 외부에서 접근할 수 없고, 클래스 내부에서만 접근 가능한 속성.
  • # 문자를 앞에 붙여 선언하며, 이를 통해 데이터 은닉을 구현할 수 있음
    1. 외부 접근 불가
    2. 접근 제어
    3. 직접적 상속 불가능 → 메서드로 간접적으로 사용해야함
  • 예제 코드
```jsx
class BankAccount {
    #balance = 0; // 프라이빗 필드

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    // 프라이빗 필드 접근 메서드
    getBalance() {
        return this.#balance;
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }
}

class SavingsAccount extends BankAccount {
    addInterest(rate) {
        // 부모 클래스의 프라이빗 필드에 직접 접근하려고 하면 오류 발생
        // this.#balance += this.#balance * rate; // 오류 발생: SyntaxError

        // 대신, 부모 클래스의 메서드를 통해 간접적으로 접근 가능
        const currentBalance = this.getBalance();
        this.deposit(currentBalance * rate);
    }
}

const savings = new SavingsAccount(1000);
console.log(savings.getBalance()); // 1000
savings.addInterest(0.05);
console.log(savings.getBalance()); // 1050
```

Getter 와 Setter

  • getter, setter는 인스턴스 프로퍼티에 접근하거나 값을 할당할 때 사용. getter, setter는 호출하는 것이 아니라 프로퍼티 처럼 참조하는 형식으로 사용하며, 참조 시에 내부적으로 getter 함수가 호출.
  • gettersetter는 프로퍼티처럼 참조. 참조에 내부적으로 getter, setter 함수가 호출.
  • getter는 이름 그대로 무언가를 취득할 때 사용하므로 반드시 무언가를 반환해야 하고 setter는 무언가를 프로퍼티에 할당해야 할 때 사용하므로 반드시 매개변수가 있어야 함.
  • 그리고 setter는 단 하나의 값만 할당 받기 때문에 단 하나의 매개변수만 선언할 수 있음.
  • 코드
      class Rectangle {
          constructor(width, height) {
              this.width = width;
              this.height = height;
          }
    
          // getter: 면적을 계산하여 반환
          get area() {
              return this.width * this.height;
          }
    
          // setter: 너비와 높이를 동시에 설정
          set dimensions({ width, height }) {
              this.width = width;
              this.height = height;
          }
      }
    
      const rect = new Rectangle(10, 5);
      console.log(rect.area); // 50
    
      // setter 사용하여 너비와 높이 변경
      rect.dimensions = { width: 7, height: 3 };
      console.log(rect.area); // 21
  • get 키워드는 속성에 접근할 때 호출되고, set 키워드는 속성에 값을 설정할 때 호출
  • Getter 와 Setter 주의할점
    1. gettersetter의 이름은 실제 속성과 겹치지 않아야 함
      • 보통 내부 속성 이름에 밑줄(_)을 붙여 충돌을 방지
    2. class Person { constructor(name) { this.name = name; } get name() { // 여기서 name이 실제 속성인 this.name과 충돌 return this.name; // 무한 재귀 호출로 오류 발생 } }
    3. setter에서 무한 재귀 방지
      • setter는 속성에 값을 설정할 때마다 호출되므로, setter 내부에서 다시 동일한 속성을 설정하면 무한 루프가 발생
      • 마찬가지로 (_) 넣어서 별도의 속성으로 사용
    4. class Circle { set radius(value) { this.radius = value; // 무한 재귀 호출 발생 } }
    5. getter의 복잡한 계산 지양해야 함
      • getter는 속성에 접근할 때마다 호출되므로 복잡한 계산을 넣으면 성능 저하를 유발할 수 있음. getter는 값 반환 외의 복잡한 작업을 최소화하는 것이 좋음

정적 메서드 (static)

  • 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
  • 정적 메서드는 클래스 자체에 속하며,new 키워드를 통해 인스턴스를 생성할 필요가 없음
  • 인스턴스 메서드는 개별 인스턴스에서 호출할 수 있으며, this를 통해 인스턴스의 속성이나 다른 메서드에 접근할 수 있음
class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // 인스턴스 메서드
    introduce() {
        return `안녕하세요, 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`;
    }

    // 정적 메서드
    static isAdult(age) {
        return age >= 18;
    }
}

// 인스턴스 생성
const user1 = new User("김동찬", 25);

// 인스턴스 메서드 호출
console.log(user1.introduce()); 
// 출력: 안녕하세요, 제 이름은 김동찬이고, 나이는 25살입니다.

// 정적 메서드 호출 (클래스를 통해 직접 호출)
console.log(User.isAdult(25)); 
// 출력: true
console.log(User.isAdult(15)); 
// 출력: false

오버라이딩

  • 오버라이딩(Overriding)은 자바스크립트에서 상속된 메서드를 자식 클래스에서 재정의하여, 부모 클래스의 기본 동작을 변경하거나 확장하는 기능. 이를 통해 자식 클래스는 부모 클래스와 동일한 메서드 이름을 사용하여 자신만의 동작을 정의할 수 있음
  • 메서드 오버라이딩 (Method Overriding)
  • class Animal { speak() { console.log("Animal makes a sound"); } } class Dog extends Animal { speak() { console.log("Dog barks"); } } const animal = new Animal(); animal.speak(); // 출력: Animal makes a sound const dog = new Dog(); dog.speak(); // 출력: Dog barks
  • 생성자 오버라이딩(Constructor Overriding)
    • super() 키워드를 사용
    • 화살표 함수에는 super() 가 없음!
    • class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // 부모 클래스 생성자 호출 this.breed = breed; // 자식 클래스 속성 초기화 } speak() { console.log(`${this.name} barks!`); } } const dog = new Dog('Buddy', 'Golden Retriever'); dog.speak(); // 출력: Buddy barks!

주제 내용
클래스 객체를 생성하기 위한 변수와 메소드를 정의하는 틀
Syntactic Sugar 논리적으로 간결한 표현, 중복 로직 간소화
일급 객체 특징 1. 변수에 할당 가능2. 함수의 인자로 전달 가능3. 함수 반환 값으로 사용 가능 4. 동적으로 생성 가능
Strict Mode 클래스 본문은 엄격한 문법 규칙이 적용됨
Constructor 클래스 내에 하나만 존재, super()로 부모 생성자 호출
클래스 선언 클래스 정의 후 사용해야 하며, 클래스 표현식은 이름이 있을 수도, 없을 수도 있음
상속 extends 키워드 사용
Private 필드 # 사용하여 선언, 외부 접근 불가, 데이터 은닉
Getter와 Setter 인스턴스 프로퍼티 접근 및 값 할당에 사용, getset 키워드 사용
정적 메서드 인스턴스 생성 없이 호출 가능한 메서드, static
오버라이딩 상속된 메서드를 재정의하여 부모 클래스의 동작 변경
메서드 오버라이딩 자식 클래스에서 부모 클래스의 메서드 재정의
생성자 오버라이딩 자식 클래스에서 부모 클래스의 생성자 호출 (super())

궁금했던 내용 자료


보면 좋을 것 같은 자료

'Front-end > JS' 카테고리의 다른 글

[JS] 실행 컨텍스트와 콜 스택  (0) 2024.11.07