생성자(Constructor
) 메서드는 클래스의 특별한 메서드로서 해당 클래스의 객체 인스턴스를 생성하고 초기화하는 역할을 합니다.
class Polygon {
constructor() {
this.name = 'Polygon';
}
}
const poly1 = new Polygon();
console.log(poly1.name);
// 예상 출력: "Polygon"
생성자 메서드의 문법은 다음과 같습니다.
constructor() { /* ... */ }
constructor(argument0) { /* ... */ }
constructor(argument0, argument1) { /* ... */ }
constructor(argument0, argument1, /* ..., */ argumentN) { /* ... */ }
일부 추가적인 문법 제약사항이 있습니다.
constructor
라는 이름의 클래스 메서드는 게터(getter
), 세터(setter
), async
, generator
로 선언될 수 없습니다.constructor
메서드를 가질 수 없습니다.생성자는 인스턴스화된 객체에 대해 다른 메서드를 호출하기 전에 수행해야 할 사용자 정의 초기화 작업을 제공합니다.
class Person {
constructor(name) {
this.name = name;
}
introduce() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}
}
const otto = new Person("Otto");
otto.introduce(); // 안녕하세요, 제 이름은 Otto입니다.
사용자 정의 생성자를 제공하지 않는 경우, 자동으로 기본 생성자가 제공됩니다. 기본 생성자는 기본 클래스인 경우 비어있습니다.
constructor() {}
constructor(...args) {
super(...args);
}
참고: 위의 예시와 같은 명시적인 생성자와 기본 생성자의 차이점은 후자가 실제로 배열 반복자를
argument spreading
을 통해 호출하지 않는다는 것입니다.
이로 인해 다음과 같은 코드가 작동합니다.
class ValidationError extends Error {
printCustomerMessage() {
return `Validation failed :-( (details: ${this.message})`;
}
}
try {
throw new ValidationError("유효하지 않은 전화번호입니다");
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.name); // ValidationError 대신 Error가 출력됩니다!
console.log(error.printCustomerMessage());
} else {
console.log("알 수 없는 오류", error);
throw error;
}
}
ValidationError
클래스는 명시적인 생성자가 필요하지 않으므로 사용자 정의 생성자를 제공하지 않습니다. 기본 생성자가 부모 Error
를 인수로 초기화하는 초기화 작업을 처리합니다.
그러나 사용자 정의 생성자를 제공하고 해당 클래스가 어떤 부모 클래스를 상속받는 경우, super()
를 사용하여 명시적으로 부모 클래스 생성자를 호출해야 합니다. 예를 들면 다음과 같습니다.
class ValidationError extends Error {
constructor(message) {
super(message); // 부모 클래스 생성자 호출
this.name = "ValidationError";
this.code = "42";
}
printCustomerMessage() {
return `Validation failed :-( (details: ${this.message}, code: ${this.code})`;
}
}
try {
throw new ValidationError("유효하지 않은 전화번호입니다");
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.name); // 이제 ValidationError이 출력됩니다!
console.log(error.printCustomerMessage());
} else {
console.log("알 수 없는 오류", error);
throw error;
}
}
클래스의 생성자에는 반환 값이 있을 수 있습니다. 기본 클래스는 생성자에서 아무것도 반환할 수 있지만, 파생 클래스는 객체
또는 undefined
를 반환해야 하며 그렇지 않으면 TypeError
가 발생합니다.
class ParentClass {
constructor() {
return 1;
}
}
console.log(new ParentClass()); // ParentClass {}
// 반환 값은 객체가 아니므로 무시됩니다
// 이는 함수 생성자와 일관성이 있습니다
class ChildClass extends ParentClass {
constructor() {
return 1;
}
}
console.log(new ChildClass()); // TypeError: 파생 클래스의 생성자는 객체 또는 undefined만 반환할 수 있습니다
부모 클래스의 생성자가 객체를 반환하는 경우, 해당 객체는 파생 클래스의 class
필드에 정의될 this
값으로 사용됩니다. 이 기법은 "return overriding"
이라고 불리며 파생 클래스의 필드(비공개 필드 포함)가 관련 없는 객체에 정의될 수 있게 합니다.
생성자는 일반적인 메서드 구문을 따르므로 매개변수 기본값, 나머지 매개변수 등을 모두 사용할 수 있습니다.
class Person {
constructor(name = "익명") {
this.name = name;
}
introduce() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}
}
const person = new Person();
person.introduce(); // 안녕하세요, 제 이름은 익명입니다.
생성자는 반드시 리터럴 이름이어야 합니다. 계산된 속성은 생성자로 사용할 수 없습니다.
class Foo {
// 이것은 계산된 속성입니다. 생성자로 인식되지 않습니다.
["constructor"]() {
console.log("호출됨");
this.a = 1;
}
}
const foo = new Foo(); // 로그 없음
console.log(foo); // Foo {}
foo.constructor(); // "호출됨" 출력
console.log(foo); // Foo { a: 1 }
async
메서드, generator
메서드, 접근자(accessor)
, 클래스 필드는 생성자로 호출될 수 없습니다. 또한 private
이름은 #constructor
로 호출될 수 없습니다. constructor
라는 이름의 멤버는 일반 메서드이어야 합니다.
이 코드 스니펫은 클래스 예제에서 가져온 것입니다 (실제 데모).
class Square extends Polygon {
constructor(length) {
// 여기서 Polygon의 너비와 높이에 제공된 길이로
// 부모 클래스의 생성자를 호출합니다.
super(length, length);
// 참고: 파생 클래스에서 `super()`를 사용하기 전에
// `this`를 사용할 수 있도록 해야 합니다.
// 이를 생략하면 ReferenceError가 발생합니다.
this.name = "Square";
}
get area() {
return this.height * this.width;
}
set area(value) {
this.height = value ** 0.5;
this.width = value ** 0.5;
}
}
super()
는 현재 클래스의 프로토타입의 생성자를 호출합니다. 현재 클래스 자체의 프로토타입을 변경하면 super()
는 새로운 프로토타입의 생성자를 호출합니다. 현재 클래스의 prototype
속성의 프로토타입을 변경해도 super()
가 호출하는 생성자는 영향을 받지 않습니다.
class Polygon {
constructor() {
this.name = "Polygon";
}
}
class Rectangle {
constructor() {
this.name = "Rectangle";
}
}
class Square extends Polygon {
constructor() {
super();
}
}
// Square을 Polygon이 아닌 Rectangle (기본 클래스)을 상속하도록 변경
Object.setPrototypeOf(Square, Rectangle);
const newInstance = new Square();
// newInstance는 여전히 Polygon의 인스턴스입니다. Square.prototype을
// 변경하지 않았으므로 newInstance의 프로토타입 체인은 여전히 다음과 같습니다.
// newInstance --> Square.prototype --> Polygon.prototype
console.log(newInstance instanceof Polygon); // true
console.log(newInstance instanceof Rectangle); // false
// 그러나 super()가 Rectangle로 호출되므로 newInstance의 name 속성은
// Rectangle의 로직으로 초기화됩니다.
console.log(newInstance.name); // Rectangle
사실 지금 이렇게 번역했지만... 내용을 전부 이해하지는 못했다. 그래도 나중에 다시 번역해서 이해하기 보다는 미리 번역해두면 더 편할 것 같아서 미리 번역해두는 거다. 일단 간단하게 예습 정도 해야할 일이 있어서 이렇게 글을 작성했고 다음에 더 깊게 공부하게 된다면, 해당 자료를 꼭 참고해야겠다.