[JavaScript]_Class

hanseungjune·2023년 7월 6일
0

JavaScript

목록 보기
84/87
post-thumbnail

📃 원본 링크

🖨️ 번역본

JavaScript 클래스

JavaScript에서 클래스는 객체 지향 프로그래밍을 구현하는 데 사용되는 개념입니다. 클래스를 사용하여 객체를 생성하고, 속성과 메서드를 정의할 수 있습니다.

클래스 정의하기

클래스는 class 키워드를 사용하여 정의됩니다. 클래스 이름은 대문자로 시작하는 관례를 따릅니다.

class MyClass {
  // 클래스의 내용
}

클래스 생성자

클래스 내부에 constructor 메서드를 정의하여 클래스의 인스턴스를 초기화할 수 있습니다. 생성자는 new 키워드로 클래스의 인스턴스를 생성할 때 자동으로 호출됩니다.

class MyClass {
  constructor(name) {
    this.name = name;
  }
}

클래스 메서드

클래스는 메서드를 정의할 수 있습니다. 메서드는 클래스의 함수로, 클래스의 인스턴스에서 호출할 수 있습니다.

class MyClass {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`안녕하세요, ${this.name}님!`);
  }
}

클래스 상속

클래스는 다른 클래스를 상속하여 확장할 수 있습니다. 상속은 기존 클래스의 속성과 메서드를 가져와 새로운 클래스에 추가하는 기능을 제공합니다. extends 키워드를 사용하여 상속할 클래스를 지정합니다.

class ChildClass extends ParentClass {
  // 클래스의 내용
}

코드 예시

아래는 클래스를 사용하여 간단한 동물 클래스와 하위 클래스를 구현하는 예시입니다.

class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name}이(가) 먹이를 먹습니다.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name}이(가) 멍멍 짖습니다.`);
  }
}

const dog = new Dog('멍멍이');
dog.eat(); // 멍멍이이(가) 먹이를 먹습니다.
dog.bark(); // 멍멍이이(가) 멍멍 짖습니다.

클래스 정의하기

클래스는 사실 "특별한 함수"이며, 함수 표현식과 함수 선언과 같이 두 가지 방법으로 정의할 수 있습니다: 클래스 표현식(Class Expression)과 클래스 선언(Class Declaration).

// 선언 (Declaration)
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

// 표현식 (Expression); 클래스는 익명이지만 변수에 할당됩니다.
const Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// 표현식 (Expression); 클래스는 자체 이름을 가집니다.
const Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

함수 표현식과 마찬가지로, 클래스 표현식은 익명일 수도 있고, 변수와는 다른 이름을 가질 수도 있습니다. 그러나 함수 선언과는 달리, 클래스 선언은 let 또는 const와 같은 temporal dead zone 제한을 가지며 호이스팅되지 않은 것처럼 동작합니다.

클래스 본문(Class body)

클래스의 본문은 중괄호 {} 안에 있는 부분을 말합니다. 이곳에서 메서드나 생성자와 같은 클래스 멤버를 정의합니다.

클래스의 본문은 "use strict" 지시문 없이도 엄격 모드(strict mode)에서 실행됩니다.

클래스 멤버는 세 가지 측면으로 특징을 지닙니다:

  • 종류(Kind): Getter, setter, 메서드 또는 필드
  • 위치(Location): 정적(static) 또는 인스턴스(instance)
  • 가시성(Visibility): 공개(public) 또는 비공개(private)

이들은 합쳐서 16가지 가능한 조합을 만들어냅니다. 참조를 더욱 논리적으로 분할하고 내용이 겹치지 않도록하기 위해, 다른 요소들은 따로 설명하는 페이지에서 자세히 소개됩니다:

메서드 정의
공개(public) 인스턴스 메서드

게터(getter)
공개(public) 인스턴스 게터

세터(setter)
공개(public) 인스턴스 세터

공개(public) 클래스 필드
공개(public) 인스턴스 필드

정적(static)
공개(public) 정적(static) 메서드, 게터, 세터, 필드

비공개(private) 클래스 멤버
비공개(private) 모든 요소

참고: 비공개(private) 요소는 동일한 클래스 내에서 선언된 모든 속성 이름이 고유해야 하는 제약이 있습니다. 다른 모든 공개(public) 속성은 이러한 제약을 가지지 않습니다. 이름이 같은 여러 공개(public) 속성을 가질 수 있으며, 마지막으로 선언된 속성이 다른 속성들을 덮어씁니다. 이는 객체 초기화와 동일한 동작입니다.

또한, 생성자와 정적 초기화 블록(static initialization blocks)이라는 두 가지 특수한 클래스 멤버 구문이 있습니다. 이들은 각각 자체의 참조를 가지고 있습니다.

생성자(Constructor)

생성자 메서드는 클래스로 생성된 객체를 생성하고 초기화하기 위한 특수한 메서드입니다. 클래스 내에 "constructor"라는 이름의 특수한 메서드는 하나만 있어야 합니다. 클래스에 생성자 메서드가 여러 개 있는 경우 SyntaxError가 발생합니다.

생성자는 super 키워드를 사용하여 슈퍼 클래스의 생성자를 호출할 수 있습니다.

생성자 내부에서 인스턴스 속성을 생성할 수 있습니다.

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

또는, 인스턴스 속성의 값이 생성자의 인수에 의존하지 않는 경우, 클래스 필드로 정의할 수 있습니다.

정적 초기화 블록(Static initialization blocks)

정적 초기화 블록은 정적 속성의 유연한 초기화를 가능하게 하며, 초기화 중에 문장을 평가할 수 있으며, 비공개(private) 범위에 접근할 수 있습니다.

여러 개의 정적 블록을 선언할 수 있으며, 이들은 정적 필드와 메서드의 선언과 번갈아 가며 선언될 수 있습니다(모든 정적 항목은 선언 순서대로 평가됩니다).

메서드(Methods)

메서드는 각 클래스 인스턴스의 프로토타입에 정의되며, 모든 인스턴스에서 공유됩니다. 메서드는 일반 함수, 비동기 함수, 제너레이터 함수, 비동기 제너레이터 함수가 될 수 있습니다. 자세한 내용은 메서드 정의를 참조하세요.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // 게터(Getter)
  get area() {
    return this.calcArea();
  }
  // 메서드(Method)
  calcArea() {
    return this.height * this.width;
  }
  *getSides() {
    yield this.height;
    yield this.width;
    yield this.height;
    yield this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]

위 예시에서는 사각형 클래스인 Rectangle을 사용하여 객체 square를 생성하고, area 게터와 calcArea 메서드, 그리고 getSides 제너레이터 메서드를 정의하고 있습니다.

정적 메서드와 필드(Static methods and fields)

static 키워드는 클래스의 정적 메서드나 필드를 정의합니다. 정적 속성(필드와 메서드)은 각 인스턴스마다 정의되는 것이 아니라 클래스 자체에 정의됩니다. 정적 메서드는 종종 애플리케이션에서 유틸리티 함수를 생성하는 데 사용되며, 정적 필드는 캐시, 고정된 설정 또는 인스턴스 간에 복제할 필요가 없는 데이터와 같은 경우에 유용합니다.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static displayName = "Point";
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.hypot(dx, dy);
  }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined

console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755

필드 선언

클래스 필드 선언 구문을 사용하면, 생성자 예시를 다음과 같이 작성할 수 있습니다.

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

클래스 필드는 변수가 아닌 객체 속성과 유사하므로, const와 같은 키워드를 사용하여 선언하지 않습니다. JavaScript에서는 비공개(private) 요소가 특별한 식별자 구문을 사용하므로, publicprivate와 같은 변경자 키워드를 사용해서는 안 됩니다.

위에서 보듯이, 필드는 기본값과 함께 선언할 수도 있습니다. 기본값이 없는 필드는 undefined로 기본 설정됩니다. 필드를 미리 선언함으로써, 클래스 정의가 자체적으로 문서화되고 필드는 항상 존재하므로 최적화에 도움이 됩니다.

자세한 내용은 공개(public) 클래스 필드를 참조하세요.

비공개(private) 클래스 멤버

비공개(private) 필드를 사용하여 정의를 개선할 수 있습니다.

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

비공개(private) 필드는 클래스 외부에서 참조하는 것은 오류입니다. 클래스 내부에서만 읽거나 쓸 수 있습니다. 클래스 외부에서는 내부 사항에 의존할 수 없도록 비공개(private)로 정의함으로, 버전 간에 변경될 수 있는 내부 사항에 대한 사용자 의존을 방지할 수 있습니다.

비공개(private) 필드는 필드 선언 구문에서 미리 선언해야 합니다. 일반 속성과는 달리, 일반 속성처럼 할당을 통해 나중에 생성할 수는 없습니다.

자세한 내용은 비공개(private) 클래스 멤버를 참조하세요.

상속

extends 키워드는 클래스 선언이나 클래스 표현식에서 다른 생성자(클래스 또는 함수)의 자식 클래스를 생성하기 위해 사용됩니다.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // 슈퍼 클래스의 생성자를 호출하고 name 매개변수를 전달합니다.
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.

자식 클래스에 생성자가 있는 경우, super()를 호출한 후에 this를 사용해야 합니다. super 키워드는 슈퍼 클래스의 해당 메서드를 호출하는 데에도 사용할 수 있습니다.

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(`${this.name} roars.`);
  }
}

const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

순서

클래스 선언이나 클래스 표현식이 평가될 때, 다음과 같은 순서로 여러 구성 요소가 평가됩니다:

  1. extends 절이 있다면, 먼저 평가됩니다. 유효한 생성자 함수 또는 null로 평가되어야 하며, 그렇지 않으면 TypeError가 발생합니다.
  2. constructor 메서드가 추출됩니다. constructor가 없으면 기본 구현으로 대체됩니다. 그러나 constructor 정의는 메서드 정의일 뿐이므로 이 단계는 관찰될 수 없습니다.
  3. 클래스 요소의 속성 키가 선언된 순서대로 평가됩니다. 속성 키가 계산적이면 계산식이 평가되며, 이 때 this 값은 클래스를 둘러싼 this 값으로 설정됩니다. 속성 값은 아직 평가되지 않습니다.
  4. 메서드와 접근자들이 선언된 순서대로 설치됩니다. 인스턴스 메서드와 접근자는 현재 클래스의 prototype 속성에 설치되고, 정적 메서드와 접근자는 클래스 자체에 설치됩니다. 비공개(private) 인스턴스 메서드와 접근자는 나중에 인스턴스에 직접 설치하기 위해 저장됩니다. 이 단계는 관찰될 수 없습니다.
  5. extends로 지정된 프로토타입과 constructor로 지정된 구현을 가지고 클래스가 초기화됩니다. 위의 모든 단계에서, 평가된 식이 클래스의 이름에 접근하려고 할 때, 클래스가 아직 초기화되지 않았으므로 ReferenceError가 발생합니다.
  6. 클래스 요소의 값들이 선언된 순서대로 평가됩니다:
    • 각 인스턴스 필드(공개 또는 비공개)의 초기화식이 저장됩니다. 초기화식은 인스턴스 생성시, 생성자의 시작 부분(기본 클래스의 경우) 또는 super() 호출이 반환되기 바로 전에 평가됩니다.
    • 각 정적 필드(공개 또는 비공개)의 초기화식이 클래스 자체를 this로 설정하고 평가됩니다. 그리고 해당 필드가 클래스에 생성됩니다.
    • 정적 초기화 블록이 클래스 자체를 this로 설정하고 평가됩니다.
  7. 이제 클래스는 완전히 초기화되어 생성자 함수로 사용될 수 있습니다.

인스턴스 생성 방법에 대해서는 생성자 참조를 참고하세요.

예시

인스턴스 메서드와 정적 메서드에서 this 바인딩

인스턴스 메서드나 정적 메서드를 변수에 할당한 후 호출할 때 this 값을 명시적으로 지정하지 않으면, 해당 메서드 내에서 this 값은 정의되지 않은(undefined) 상태가 됩니다. 이 동작은 "use strict" 지시문이 존재하지 않아도 동일하게 발생합니다. 이는 클래스 본문 내의 코드는 항상 strict mode에서 실행되기 때문입니다.

class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

const obj = new Animal();
obj.speak(); // Animal 객체
const speak = obj.speak;
speak(); // undefined

Animal.eat(); // 클래스 Animal
const eat = Animal.eat;
eat(); // undefined

위의 코드를 비 strict mode에서 전통적인 함수 기반 구문을 사용하여 다시 작성하면, 메서드 호출은 자동으로 globalThis에 바인딩됩니다. strict mode에서는 this의 값이 여전히 undefined로 유지됩니다.

function Animal() {}

Animal.prototype.speak = function () {
  return this;
};

Animal.eat = function () {
  return this;
};

const obj = new Animal();
const speak = obj.speak;
speak(); // 전역 객체 (strict mode가 아닌 경우)

const eat = Animal.eat;
eat(); // 전역 객체 (strict mode가 아닌 경우)

후기

사실 지금 이렇게 번역했지만... 내용을 전부 이해하지는 못했다. 그래도 나중에 다시 번역해서 이해하기 보다는 미리 번역해두면 더 편할 것 같아서 미리 번역해두는 거다. 일단 간단하게 예습 정도 해야할 일이 있어서 이렇게 글을 작성했고 다음에 더 깊게 공부하게 된다면, 해당 자료를 꼭 참고해야겠다.

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글