25.1. 클래스는 프로토타입의 문법적 설탕인가?
- 자바스크립트는 프로토타입 기반 객체지향 언어
- 클래스 없이도 생성자 함수와 프로토타입을 통해 상속 구현 가능
- ES6 클래스 도입
- 기존 프로토타입 기반 패턴 -> 클래스 기반 패턴으로 사용
- 클래스와 생성자 함수의 차이점
- 클래스는 new 연산자 없이 호출하면 에러 발생, 생성자 함수는 일반 함수로 호출
- 클래스는 상속을 지원하는 extends와 super 키워드 제공
- 클래스는 호이스팅이 발생하지 않는 것처럼 동작함, 생성자 함수는 함수 호이스팅과 변수 호이스팅 발생
- 클래스는 암묵적으로 strict mode 지정하여 해제 불가
- 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 열거되지 않음 ([[Enumerable]] = false)
- 단순한 문법적 설탕보다는 새로운 객체 생성 메커니즘
25.2. 클래스 정의
- class 키워드 사용
- 일반적으로 파스칼 케이스 사용
- 클래스는 함수, 표현식 사용 가능 = 일급 객체
- 무명의 리터럴 생성 가능 (런타임 생성)
- 변수나 자료구조(객체, 배열 등) 저장 가능
- 함수의 매개변수로 전달 가능
- 함수의 반환값으로 사용 가능
- 클래스 몸체에 정의할 수 있는 메서드
- constructor (생성자)
- 프로토타입 메서드
- 정적 메서드
- 클래스와 생성자 함수의 정의 방식은 형태가 매우 유사함
25.3. 클래스 호이스팅
- 클래스는 함수로 평가됨
- 클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 런타임 이전에 평가되어 객체를 생성함
- 생성된 함수 객체는 constructor
- 프로토타입도 함께 생성됨
- 생성자 함수와 프로토타입은 항상 같이 존재함
- 단, 클래스는 정의 이전에 참조 불가
- 이때문에 호이스팅이 발생하지 않는 것처럼 보이지만 실제로는 발생함
- 일시적 사각지대 (temporal dead zone, TDZ)
- var, let, const, function, function*, class 키워드는 모두 호이스팅 발생
25.4. 인스턴스 생성
- 클래스는 생성자 함수로, new 연산자와 함께 호출하여 인스턴스 생성
- 클래스는 인스턴스 생성이 목적이므로 반드시 new 연산자와 함께 호출해야 함
- 클래스 표현식으로 정의된 경우 식별자를 사용하면 에러 발생
25.5. 메서드
- 클래스 몸체에는 0개 이상의 메서드 선언 가능
25.5.1. constructor
class Person {
constructor(name) {
this.name = name;
}
}
- cf. 클래스의 constructor 메서드와 프로토타입의 constructor 프로퍼티는 관련 없음
- constructor는 인스턴스를 생성 및 초기화하는 특수한 메서드
- 이름 변경 불가
- prototype.constructor: 클래스 자신을 가리킴
- 클래스가 인스턴스를 생성하는 생성자 함수라는 것을 의미
- constructor는 메서드가 아닌 함수 객체 코드의 일부
- 클래스 정의가 평가될 때 constructor의 코드로 동작하는 함수 객체 생성
- 생성자 함수와의 차이점
- 클래스 내 하나만 존재: 2개 이상 포함하면 문법 에러(SyntaxError) 발생
- constructor 생략 가능: 생략하면 암묵적으로 빈 constructor 정의되어 빈 객체 생성함
- 인스턴스 초기화: 고정값 또는 매개변수로 설정 가능
- return문 사용 지양: 명시적으로 반환하면 암묵적으로 this 반환이 무시됨
class Person {
constructor(address) {
this.name = 'Lee';
this.address = address
}
}
const me = new Person('Seoul');
console.log(me);
25.5.2. 프로토타입 메서드
- 클래스 메서드는 기본적으로 프로토타입 메서드
- 클래스가 생성한 인스턴스는 프로토타입 체인이 동일하게 적용됨
- 클래스는 생성자 함수와 마찬가지로 프로토타입 기반의 객체 생성 메커니즘
25.5.3. 정적 메서드
- 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
- 클래스에서는 static 키워드로 정적 메서드 가능
static sayHi() { console.log('Hi!') }
- 정적 메서드는 클래스에 바인딩된 메서드
- 클래스는 클래스 평가 시점에 함수 객체가 됨
- 정적 메서드는 클래스 정의 이후 인스턴스 생성 없이 클래스로 호출 가능
- 정적 메서드는 인스턴스로 호출 불가
- 인스턴스의 프로토타입 체인 상에는 클래스가 존재하지 않기 때문에 상속받을 수 없음
25.5.4. 정적 메서드와 프로토타입 메서드의 차이
- 정적 메서드와 프로토타입 메서드는 속해 있는 프로토타입 체인이 다름
- 정적 메서드는 클래스로 호출, 프로토타입 메서드는 인스턴스로 호출
- 정적 메서드는 인스턴스 프로퍼티 참조 불가, 프로토타입 메서드는 인스턴스 프로퍼티 참조 가능
- 메서드에서 인스턴스 프로퍼티를 참조해야 한다면 프로토타입 메서드 사용해야 함
- 메서드 내부의 this는 소유한 객체가 아닌 호출한 객체(마침표 연산자 앞의 객체)에 바인딩
- 프로토타입 메서드는 인스턴스로 호출 (ex.
me.sayHi())
- 정적 메서드는 클래스로 호출 (ex.
Person.sayHi())
- 따라서 this를 사용하는 메서드는 프로토타입 메서드, 사용하지 않는 메서드는 정적 메서드로 권장
- 표준 빌트인 객체들은 다양한 정적 메서드를 가지고 있음
- 애플리케이션 전역에서 사용할 유틸리티 함수
- 클래스 또는 생성자 함수를 하나의 네임스페이스로 사용하면 구조화 효과
25.5.5. 클래스에서 정의한 메서드의 특징
- function 키워드를 생략한 메서드 축약 표현 사용
- 메서드 정의 시 콤마 사용하지 않음
- 암묵적으로 strict mode 실행
- [[Enumerable]] = false, 열거 불가
- non-constructor, new 연산자와 함께 호출 불가
25.6. 클래스의 인스턴스 생성 과정
1. 인스턴스 생성과 this 바인딩
- new 연산자 클래스 호출
- 암묵적으로 빈 객체 인스턴스 생성
- 인스턴스의 프로토타입 설정 및 this 바인딩
2. 인스턴스 초기화
- constructor 실행 및 인스턴스 초기화
3. 인스턴스 반환
25.7. 프로퍼티
25.7.1. 인스턴스 프로퍼티
- 인스턴스 프로퍼티는 항상 public
- constructor 내부에서 this에 추가한 프로퍼티는 항상 클래스가 생성한 인스턴스의 프로퍼티가 됨
25.7.2. 접근자 프로퍼티
- 자체적으로 값을 갖지 않고, 다른 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
- getter: get 키워드
- 인스턴스 프로퍼티에 접근할 때 사용
- 반환 값 필수
- setter: set 키워드
- 인스턴스 프로퍼티에 값을 할당하거나 조작할 때 사용
- 매개변수 필수
25.7.3. 클래스 필드 정의 제안
- 클래스 필드는 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리킴
- 자바스크립트의 클래스 몸체에 메서드가 아닌 클래스 필드를 선언하면 문법 에러 발생
- But, ES10 (ECMA2019) private field 지원
- this는 constructor와 메서드 내에서만 유효함
- constructor 클래스 필드를 초기화하지 않으면 undefined 가짐
- 함수는 일급 객체 -> 클래스 필드에 할당 가능
- 인스턴스 프로퍼티 정의하는 방식
- constructor 인스턴스 프로퍼티 정의
- 클래스 필드 정의 제안
25.7.4. private 필드 정의 제안
- ES10 (ECMA2019) private field 지원
- private 필드 선두 # 키워드 선언 및 참조
- 타입스크립트 접근 제한자(public, private, protected) 모두 가능
class Person {
#name = '';
constructor(name) {
this.#name = name;
}
}
const me = new Person('Lee');
console.log(me.#name);
- 외부에서 직접 접근 불가
- constructor 안에 private 필드 정의 에러
25.7.5. static 필드 정의 제안
- ES13 (ECMA2022) static private 필드 및 메서드 도입
25.8. 상속에 의한 클래스 확장
- 기존 클래스를 상속받아 새로운 클래스를 확장(extends)할 수 있음
25.8.2. extends 키워드
- 수퍼클래스 = 베이스 클래스 = 부모 클래스: 상속된 상위 클래스
- 서브클래스 = 파생 클래스 = 자식 클래스: 상속을 통해 확장된 클래스
- 인스턴스 프로토타입 체인뿐만 아니라 클래스 간 프로토타입 체인도 생성
- 프로토타입 메서드, 정적 메서드 모두 상속 가능
25.8.3. 동적 상속
- 클래스뿐만 아니라 생성자 함수 상속 가능
- [[Constructor]] 내부 메서드를 갖는 모든 표현식 사용 가능
- 단, extends 키워드 앞에는 반드시 클래스가 와야함
25.8.4. 서브클래스의 constructor
- constructor 생략 시 암묵적으로 비어있는 constructor 정의됨
- 서브클래스에서는 수퍼클래스의 constructor 호출하여 인스턴스 생성
- 모두 생략하면 빈 객체 생성됨
25.8.5. super 키워드
- super 키워드: 함수처럼 호출 가능, this 식별자처럼 참조할 수 있는 특수한 키워드
super 호출
- 수퍼클래스의 constructor 호출
- 수퍼클래스의 constructor 그대로 생성하는 경우 서브클래스 생략 가능
- new 연산자와 함께 전달한 인수를 super 호출을 통해 수퍼클래스로 전달 가능
- 서브클래스의 constructor를 생략하지 않는 경우 반드시 super 호출해야 함
- 서브클래스의 constructor에서 super 호출하기 전까지는 this 참조 불가
- 반드시 서브클래스의 constructor에서만 호출 가능, 이외 에러 발생
super 참조
- 수퍼클래스의 메서드 호출
- [[HomeObject]]를 가진 함수만 super 참조 가능
- ES6의 메서드 축약 표현으로 정의된 함수만 내부 슬롯을 갖고 super 참조 가능
- 서브클래스 메서드에서 사용
25.8.6. 상속 클래스의 인스턴스 생성 과정
1. 서브클래스의 super 호출
- [[ConstructorKind]]
- 상속받지 않는 클래스는 'base'
- 상속받는 서브클래스는 'derived'
- 서브클래스는 직접 인스턴스 생성하지 않고 수퍼클래스에 위임
- 반드시 서브클래스의 constructor에서 super 호출하는 이유
2. 수퍼클래스의 인스턴스 생성과 this 바인딩
- 수퍼클래스가 생성했지만, new.target이 가리키는 서브클래스가 생성한 것으로 처리됨
- 생성된 인스턴스의 프로토타입은 서브클래스의 프로토타입 객체
3. 수퍼클래스의 인스턴스 초기화
- 수퍼클래스의 constructor 실행되어 인스턴스 초기화
4. 서브클래스 constructor로의 복귀와 this 바인딩
- super 호출 종료 및 인스턴스 반환
- this에 인스턴스 바인딩하여 사용
5. 서브클래스의 인스턴스 초기화
- 서브클래스의 constructor 실행하여 인스턴스 초기화
6. 인스턴스 반환
25.8.7. 표준 빌트인 생성자 함수 확장
- 표준 빌트인 객체도 extends 키워드 사용하여 확장 가능
[출처] 모던 자바스크립트, Deep Dive