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
제한을 가지며 호이스팅되지 않은 것처럼 동작합니다.
클래스의 본문은 중괄호 {}
안에 있는 부분을 말합니다. 이곳에서 메서드나 생성자와 같은 클래스 멤버를 정의합니다.
클래스의 본문은 "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"
라는 이름의 특수한 메서드는 하나만 있어야 합니다. 클래스에 생성자 메서드가 여러 개 있는 경우 SyntaxError
가 발생합니다.
생성자는 super
키워드를 사용하여 슈퍼 클래스의 생성자를 호출할 수 있습니다.
생성자 내부에서 인스턴스 속성을 생성할 수 있습니다.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
또는, 인스턴스 속성의 값이 생성자의 인수에 의존하지 않는 경우, 클래스 필드로 정의할 수 있습니다.
정적 초기화 블록은 정적 속성의 유연한 초기화를 가능하게 하며, 초기화 중에 문장을 평가할 수 있으며, 비공개(private
) 범위에 접근할 수 있습니다.
여러 개의 정적 블록을 선언할 수 있으며, 이들은 정적 필드와 메서드의 선언과 번갈아 가며 선언될 수 있습니다(모든 정적 항목은 선언 순서대로 평가됩니다).
메서드는 각 클래스 인스턴스의 프로토타입에 정의되며, 모든 인스턴스에서 공유됩니다. 메서드는 일반 함수, 비동기 함수, 제너레이터 함수, 비동기 제너레이터 함수가 될 수 있습니다. 자세한 내용은 메서드 정의를 참조하세요.
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
키워드는 클래스의 정적 메서드나 필드를 정의합니다. 정적 속성(필드와 메서드)은 각 인스턴스마다 정의되는 것이 아니라 클래스 자체에 정의됩니다. 정적 메서드는 종종 애플리케이션에서 유틸리티 함수를 생성하는 데 사용되며, 정적 필드는 캐시, 고정된 설정 또는 인스턴스 간에 복제할 필요가 없는 데이터와 같은 경우에 유용합니다.
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
) 요소가 특별한 식별자 구문을 사용하므로, public
과 private
와 같은 변경자 키워드를 사용해서는 안 됩니다.
위에서 보듯이, 필드는 기본값과 함께 선언할 수도 있습니다. 기본값이 없는 필드는 undefined
로 기본 설정됩니다. 필드를 미리 선언함으로써, 클래스 정의가 자체적으로 문서화되고 필드는 항상 존재하므로 최적화에 도움이 됩니다.
자세한 내용은 공개(public
) 클래스 필드를 참조하세요.
비공개(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.
클래스 선언이나 클래스 표현식이 평가될 때, 다음과 같은 순서로 여러 구성 요소가 평가됩니다:
extends
절이 있다면, 먼저 평가됩니다. 유효한 생성자 함수 또는 null
로 평가되어야 하며, 그렇지 않으면 TypeError
가 발생합니다.constructor
메서드가 추출됩니다. constructor
가 없으면 기본 구현으로 대체됩니다. 그러나 constructor
정의는 메서드 정의일 뿐이므로 이 단계는 관찰될 수 없습니다.this
값은 클래스를 둘러싼 this
값으로 설정됩니다. 속성 값은 아직 평가되지 않습니다.prototype
속성에 설치되고, 정적 메서드와 접근자는 클래스 자체에 설치됩니다. 비공개(private
) 인스턴스 메서드와 접근자는 나중에 인스턴스에 직접 설치하기 위해 저장됩니다. 이 단계는 관찰될 수 없습니다.extends
로 지정된 프로토타입과 constructor
로 지정된 구현을 가지고 클래스가 초기화됩니다. 위의 모든 단계에서, 평가된 식이 클래스의 이름에 접근하려고 할 때, 클래스가 아직 초기화되지 않았으므로 ReferenceError
가 발생합니다.super
() 호출이 반환되기 바로 전에 평가됩니다.this
로 설정하고 평가됩니다. 그리고 해당 필드가 클래스에 생성됩니다.this
로 설정하고 평가됩니다.인스턴스 생성 방법에 대해서는 생성자 참조를 참고하세요.
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가 아닌 경우)
사실 지금 이렇게 번역했지만... 내용을 전부 이해하지는 못했다. 그래도 나중에 다시 번역해서 이해하기 보다는 미리 번역해두면 더 편할 것 같아서 미리 번역해두는 거다. 일단 간단하게 예습 정도 해야할 일이 있어서 이렇게 글을 작성했고 다음에 더 깊게 공부하게 된다면, 해당 자료를 꼭 참고해야겠다.