[JavaScript]_Public class fields

hanseungjune·2023년 7월 6일
0

JavaScript

목록 보기
87/87
post-thumbnail

📃 원본 링크

🖨️ 번역본

공개(public) 클래스 필드

공개(public) 필드는 쓰기 가능한(writable), 열거 가능한(enumerable), 구성 가능한(configurable) 속성입니다. 이러한 이유로 비공개(private) 필드와는 달리 프로토타입 상속에 참여합니다.

구문

class ClassWithField {
  instanceField;
  instanceFieldWithInitializer = "인스턴스 필드";
  static staticField;
  static staticFieldWithInitializer = "정적 필드";
}

추가적인 구문 제한 사항이 있습니다:

  • 정적 속성(필드 또는 메서드)의 이름은 prototype이 될 수 없습니다.
  • 클래스 필드(정적 또는 인스턴스)의 이름은 constructor가 될 수 없습니다.

설명

이 페이지에서는 공개 인스턴스 필드에 대해 자세히 설명합니다.

  • 정적 필드에 대해서는 static을 참조하세요.
  • 비공개 필드에 대해서는 비공개 클래스 기능을 참조하세요.
  • 공개 메서드에 대해서는 메서드 정의를 참조하세요.
  • 공개 접근자에 대해서는 gettersetter를 참조하세요.

공개 인스턴스 필드는 클래스의 모든 생성된 인스턴스에 존재합니다. 공개 필드를 선언함으로써 필드가 항상 존재하고 클래스 정의가 더욱 명확해집니다.

공개 인스턴스 필드는 인스턴스 생성 시에 기본 클래스에서 생성자가 실행되기 전에 인스턴스에 추가됩니다. 초기값이 없는 필드는 undefined로 초기화됩니다. 프로퍼티와 마찬가지로 필드 이름은 계산될 수 있습니다.

const PREFIX = "접두사";

class ClassWithField {
  field;
  fieldWithInitializer = "인스턴스 필드";
  [`${PREFIX}Field`] = "접두사가 붙은 필드";
}

const instance = new ClassWithField();
console.log(Object.hasOwn(instance, "field")); // true
console.log(instance.field); // undefined
console.log(instance.fieldWithInitializer); // "인스턴스 필드"
console.log(instance.prefixField); // "접두사가 붙은 필드"

계산된 필드 이름은 클래스 정의 시에 한 번만 평가됩니다. 이는 각 클래스마다 항상 고정된 필드 이름 집합을 가지고 있으며, 계산된 이름을 통해 두 인스턴스가 다른 필드 이름을 가질 수 없음을 의미합니다. 계산된 표현식 내부의 this 값은 클래스 정의를 둘러싼 this를 가리키며, 클래스의 이름을 참조하면 ReferenceError가 발생합니다. awaityield는 이 표현식에서 기대한 대로 작동합니다.

class C {
  [Math.random()] = 1;
}

console.log(new C());
console.log(new C());
// 두 인스턴스는 동일한 필드 이름을 가집니다.

필드 초기화자에서의 this는 생성 중인 클래스 인스턴스를 가리키며, super는 기본 클래스의 프로토타입 속성을 가리킵니다. 기본 클래스의 프로토타입에는 기본 클래스의 인스턴스 메서드가 포함되어 있지만 인스턴스 필드는 포함되어 있지 않습니다.

class Base {
  baseField = "기본 필드";
  anotherBaseField = this.baseField;
  baseMethod() {
    return "기본 메서드 출력";
  }
}

class Derived extends Base {
  subField = super.baseMethod();
}

const base = new Base();
const sub = new Derived();

console.log(base.anotherBaseField); // "기본 필드"

console.log(sub.subField); // "기본 메서드 출력"

필드 초기화 표현식은 새로운 인스턴스가 생성될 때마다 평가됩니다. (각 인스턴스에 대해 this 값이 다르기 때문에 초기화 표현식은 인스턴스별 속성에 접근할 수 있습니다.)

class C {
  obj = {};
}

const instance1 = new C();
const instance2 = new C();
console.log(instance1.obj === instance2.obj); // false

표현식은 동기적으로 평가됩니다. 초기화 표현식에서 await 또는 yield를 사용할 수 없습니다. (초기화 표현식은 암묵적으로 함수로 래핑된 것으로 생각하십시오.)

클래스 필드는 해당 생성자가 실행되기 전에 하나씩 추가됩니다. 필드 초기화자는 그 위의 필드 값을 참조할 수 있지만, 그 아래의 필드 값을 참조할 수는 없습니다. 모든 인스턴스 및 정적 메서드는 미리 추가되고 접근할 수 있으며, 초기화자에서 아래에 있는 필드를 참조하는 경우 호출하는 것이 예상한 대로 동작하지 않을 수 있습니다.

class C {
  a = 1;
  b = this.c;
  c = this.a + 1;
  d = this.c + 1;
}

const instance = new C();
console.log(instance.d); // 3
console.log(instance.b); // undefined

참고: 비공개 필드의 경우 이 점이 더 중요한데, 초기화되지 않은 비공개 필드에 접근하면 TypeError가 발생합니다. 비공개 필드가 아래에 선언되지 않은 경우라도 TypeError가 발생합니다. (비공개 필드가 선언되지 않은 경우는 이른 SyntaxError가 발생합니다.)

클래스 필드는 [[DefineOwnProperty]](즉, Object.defineProperty()) 시맨틱을 사용하여 추가되기 때문에 파생 클래스의 필드 선언은 기본 클래스의 세터에 세터를 호출하지 않습니다. 이 동작은 생성자에서 this.field = ...을 사용하는 것과 다릅니다.

class Base {
  set field(val) {
    console.log(val);
  }
}

class DerivedWithField extends Base {
  field = 1;
}

const instance = new DerivedWithField(); // 로그 없음

class DerivedWithConstructor extends Base {
  constructor() {
    super();
    this.field = 1;
  }
}

const instance2 = new DerivedWithConstructor(); // 1을 로그로 출력

참고: 클래스 필드가 [[DefineOwnProperty]] 시맨틱을 사용하는 것으로 최종화된 전에 대부분의 트랜스파일러(예: Babeltsc)는 클래스 필드를 DerivedWithConstructor 형태로 변환하여 공식화된 클래스 필드 이후에 발생한 어려운 버그를 일으킬 수 있었습니다.

클래스 필드 사용하기

클래스 필드는 생성자의 인수에 의존할 수 없으므로, 필드 초기화자는 일반적으로 각 인스턴스에 대해 동일한 값을 평가합니다 (Date.now() 또는 객체 초기화자와 같이 동일한 표현식이 각기 다른 값을 평가할 수 있는 경우를 제외하고).

class Person {
  name = nameArg; // nameArg는 생성자의 스코프 밖에 있습니다.
  constructor(nameArg) {}
}
class Person {
  // Person의 모든 인스턴스는 동일한 이름을 가집니다.
  name = "Dragomir";
}

그러나 비어있는 클래스 필드를 선언하는 것만으로도 유익합니다. 이는 필드의 존재를 나타내며, 타입 체커 및 개발자는 클래스의 구조를 정적으로 분석할 수 있습니다.

class Person {
  name;
  age;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

위의 코드는 반복적으로 보일 수 있지만, this가 동적으로 변경되는 경우를 고려해보십시오: 명시적인 필드 선언은 인스턴스에 확실히 존재할 필드를 명확히 나타냅니다.

class Person {
  name;
  age;
  constructor(properties) {
    Object.assign(this, properties);
  }
}

초기화자는 기본 클래스가 실행된 후에 평가되기 때문에, 기본 클래스 생성자에서 생성된 속성에 접근할 수 있습니다.

class Person {
  name;
  age;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Professor extends Person {
  name = `Professor ${this.name}`;
}

console.log(new Professor("Radev", 54).name); // "Professor Radev"

후기

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

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

0개의 댓글