25장 클래스
25.1 클래스는 프로토타입의 문법적 설탕인가?
- 문법적 설탕이라기 보다는 새로운 객체 생성 매커니즘으로 보는 것이 합당함
클래스 문법 vs 생성자 함수
1. new연산자 사용
- 클래스: new연산자 없이 호출하면 에러 발생
- 생성자 함수: new연산자 없이 호출하면 일반함수로서 호출
2. extends/super키워드
3. 호이스팅
- 클래스: 발생하지 않는 거서럼 동작함
- 생성자 함수: 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅, 함수 표현식으로 정의된 생성자 함수는 변수 호이스팅이 발생함
4. strict mode
- 클래스: 모든 코드에 암묵적으로 strict mode가 지정되어 해제할 수 없음
- 생성자 함수: 암묵적으로 strict mode가 지정되지 않음
5. enumerable
- 클래스: constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]값이 false이므로 열거되지 않음
25.2 클래스 정의
- 클래스 이름은 파스칼 케이스를 사용하는 것이 일반적임
// 클래스 선언문
class Person {}
// 익명 클래스 표현식
const Person = class {};
// 기명 클래스 표현식
const Person = class MyClass {};
- 클래스는 일급객체로서 다음과 같은 특징을 가짐
- 무명의 리터럴로 생성할 수 있음 (런타임 생성 가능)
- 변수나 자료구조(객체, 배열 등)에 저장할 수 있음
- 함수의 매개변수에게 전달할 수 있음
- 함수의 반환값으로 사용할 수 있음
- 클래스 몸체에서 정의할 수 있는 메서드 : constructor(생성자), 프로토타입 메서드, 정적 메서드
// 클래스 선언문
class Person {
// 생성자
constructor(name) {
this.name = name; // name 프로퍼티는 public하다
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
// 정적 메서드
static sayHello() {
console.log('Hello!');
}
}
// 인스턴스 생성
const me = new Person('Lee');
// 인스턴스 프로퍼티 참조
console.log(me.name); // Lee
// 프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee
// 정적 메서드 호출
Person.sayHello(); // Hello!
1. 클래스와 생성자 함수의 정의 방식 비교
25.3 클래스 호이스팅
- 클래스 선언문으로 정의한 클래스는 런타임 이전에 먼저 평가되어 함수 객체를 생성함(호이스팅 발생), 하지만 클래스 정의 이전에 참조할 수 없음
25.4 인스턴스 생성
- 클래스는 생성자 함수이며 new연산자와 함께 호출되어 인스턴스를 생성함
class Person {}
// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
25.5 메서드
- 클래스 몸체에는 0개 이상의 constructor(생성자), 프로토타입 메서드, 정적 메서드를 선언할 수 있음
25.5.1 constructor
- 인스턴스를 생성하고 초기화하기 위한 특수한 메서드, 이름변경 불가능
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
}
1. constructor와 생성자 함수의 차이
- constuctor는 클래스 내에 최대 한 개만 존재할 수 있음
- constuctor는 생략가능
2. 인스턴스 초기화
- 인스턴스를 초기화하려면 constructor를 생략해서는 안됨.
- 프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 constructor 내부 this에 인스턴트 프로퍼티를 추가하면 됨.
class Person {
constructor(name, address) {
// 인수로 인스턴스 초기화
this.name = name;
this.address = address;
}
}
// 인수로 초기값을 전달한다. 초기값은 constructor에 전달된다.
const me = new Person('Lee', 'Seoul');
console.log(me); // Person {name: 'Lee', address: 'Seoul'}
3. 반환문 유무
- constructor는 별도의 반환문을 갖지 않아야 함. 암묵적으로 this, 즉 인스턴스를 반환하기 때문임.
25.5.2 프로토타입 메서드
- 생성자함수와 다르게 클래스 몸체에 정의한 메서드는 클래스의 prototype프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 됨
25.5.3 정적 메서드
- 클래스에서는 메서드에 static키워드를 붙이면 정적 메서드가 됨
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 정적 메서드
static sayHi() {
console.log('Hi!');
}
}
- 정적 메서드는 인스턴스로 호출하지 않고 클래스로 호출함
Person.sayHi(); // Hi!
25.5.4 정적 메서드와 프로토타입 메서드의 차이
- 정적 메서드와 프로토타입 메서드는 자신이 속해있는 프로토타입 체인이 다름
- 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출함
- 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있음
class Square {
constructor(width, height) {
this.width = width;
this.heigth = height;
}
// 프로토타입 메서드
area() {
return this.width * this.height;
}
}
const square = new Square(10, 10);
console.log(square.area()); // 100
- 정적 메서드의 this바인드는 클래스, 프로토타입 메서드의 this바인드는 인스턴스임
25.5.5 클래스에서 정의한 메서드의 특징
- function키워드를 생략한 메서드 축약 표현을 사용
- 클래스에서 메서드를 정의할 때는 콤마가 필요 없음
- 암묵적으로 strict mode로 실행됨
- 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이므로 열거 불가능함
- 내부 메서드 [[Construct]]를 갖지 않는 non-constructor이므로 new연산자와 함께 호출할 수 없음
25.6 클래스의 인스턴스 생성 과정
1. 인스턴스 생성과 this 바인딩
암묵적으로 빈 객체(미완성인 인스턴스) 생성 -> 클래스의 인스턴스 프로토타입으로 prototype프로퍼티가 설정됨 -> 인스턴스가 this에 바인딩 됨
2. 인스턴스 초기화
constructor의 내부 코드가 실행되고 인스턴스를 초기화 함
3. 인스턴스 반환
클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됨
25.7 프로퍼티
25.7.1 인스턴스 프로퍼티
- 인스턴스 프로퍼티는 constructor 내부에서 정의해야 함
- 다른 언어처럼 private, public, protected키워드와 같은 접근제한자를 지원하지 않기 때문에 인스턴스 프로퍼티는 언제나 public임
25.7.2 접근자 프로퍼티
- 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티임 (ex. getter, setter)
const person = {
// 데이터 프로퍼티
firstName: 'Ungmo',
lastName: 'Lee',
// fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
// 배열 디스트럭처링 할당
[this.firstName, this.lastName] = name.split(' ');
}
}
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter함수가 호출된다.
person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter함수가 호출된다.
console.log(person.fullName); // Heegun Lee
- getter은 반드시 무언가를 반환해야 하고, setter은 반드시 하나의 매개변수만 있어야 함
25.7.3 클래스 필드 정의 제안
- 클래스 필드: 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
- 자바스크립트의 클래스 몸체에는 메서드만 선언할 수 있지만, 최신 브라우저/Node.js환경에서는 클래스 몸체에 클래스 필드를 선언해도 에러가 나지 않음
class Person {
// 클래스 필드 정의
name = 'Lee';
}
const me = new Person();
console.log(me); // Person {name: 'Lee'}
- 클래스 몸체에서 클래스 필드를 정의하는 경우, this에 클래스 필드를 바인딩해서는 안됨. (this는 클래스의 constructor와 메서드 내에서만 유효함)
- 클래스 필드에 초기값을 할당하지 않으면 undefined를 가짐
- 클래스 필드를 통해 메서드를 정의할 수 있음. (이 경우, 프로토타입 메서드가 아닌 인스턴스 메서드가 됨)
class Person {
// 클래스 필드에 문자열을 할당
name = 'Lee';
// 클래스 필드에 함수를 할당
getName = function () {
return this.name;
}
// 화살표 함수로 정의할 수 있다.
// getName = () => this.name;
}
25.7.4 private필드 정의 제안
- 자바스크립트에서 private필드를 정의할 때는 #을 붙임
class Person {
// private 필드 정의
#name = '';
constructor(name) {
// private 필드 참조
this.#name = name;
}
}
const me = new Person('Lee');
// private 필드 #name은 클래스 외부에서 참조할 수 없다.
console.log(me.#name);
// SyntaxError: Private field '#name' must be declared in an enclosing class
- private필드는 클래스 내부에서만 참조할 수 있음
25.7.5 static필드 정의 제안
class MyMath {
// static public 필드 정의
static PI = 22 / 7;
// static private 필드 정의
static #num = 10;
// static 메서드
static increment() {
return ++MyMath.#num;
}
}
console.log(MyMath.PI); // 3.14...
console.log(MyMath.increment()); // 11
25.8 상속에 의한 클래스 확장
25.8.1 클래스 상속과 생성자 함수 상속
- 생성자 함수 상속 : 프로토타입 체인을 통해 다른 객체의 자산을 상속
- 클래스 상속 : 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의
25.8.2 extends 키워드
- 상속을 통해 클래스를 확장하려면 extends키워드를 사용하여 상속받을 클래스를 정의함
// 수퍼(베이스/부모) 클래스
class Base {}
// 서브(파생/자식) 클래스
class Derived extends Base {}
25.8.3 동적 상속
- extends키워드는 생성자 함수도 상속받아 클래스를 확장할 수 있음. 단 extends키워드 앞에는 반드시 클래스가 와야 함.
// 생성자 함수
function Base(a) {
this.a = a;
}
// 생성자 함수를 상속받는 서브클래스
class Derived extends Base {}
const derived = new Derived(1);
console.log(derived); // Derived {a: 1}
- extends키워드 다음에 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있음. 이를 통해 동적으로 상속받을 대상을 결정할 수 있음
function Base1() {}
class Base2 {}
let condition = true;
// 조건에 따라 동적으로 상속 대상을 결정하는 서브클래스
class Derived extends (condition ? Base1 : Base2) {}
const derived = new Derived();
console.log(derived); // Derived {}
console.log(derived instanceof Base1); // true
console.log(derived instanceof Base2); // false
25.8.4 서브클래스의 constructor
- 수퍼클래스와 같이 서브클래스 모두 constructor를 생략하면 빈 객체가 생성됨.
25.8.5 super키워드
super키워드의 동작
- super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출함
- super를 참조하면 수퍼클래스의 메서드를 호출할 수 있음
super호출
- super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출함.
- super호출 주의사항 :
1.서브 클래스에서 constructor를 생략하지 않는 경우, 서브클래스의 constructor에서는 반드시 super를 호출해야 함
2.서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없음
3.super는 반드시 서브클래스의 constructor에서만 호출함
super참조
- 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있음
1.서브클래스의 프로토타입 메서드 내에서 super.sayHi는 수퍼클래스의 프로토타입 메서드 sayHi를 가리킴
2.서브클래스의 정적 메서드 내에서 super.sayHi는 수퍼클래스의 정적 메서드 sayHi를 가리킴
25.8.6 상속 클래스의 인스턴스 생성 과정
- 서브클래스의 super호출
- 수퍼클래스의 인스턴스 생성과 this바인딩
- 수퍼클래스의 인스턴스 초기화
- 서브클래스 constructor로의 복귀와 this바인딩
- 서브클래스의 인스턴스 초기화
- 인스턴스 반환
25.8.7 표준 빌트인 생성자 함수 확장
- String, Number, Array와 같은 표준 빌트인 객체도 extends키워드를 사용하여 확장할 수 있음