클래스는 생성자 함수와 매우 유사하게 동작하지만 몇 가지 차이가 있다.
// 클래스 선언문
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!
console.log(Person);
// ReferenceError: Cannot access 'Person' before initialization
// 클래스 선언문
class Person {}
클래스 선언문은 마치 호이스팅이 발생하지 않는 것처럼 보인다.
클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 마찬가지로 호이스팅이 발생한다.
일지적 사각지대(TDZ)란?
변수가 선언되기 전에 접근하려고 할 때 발생하는 현상. 변수가 선언되었지만 아직 초기화 되지 않은 상태를 말한다. 변수가 선언된 위치부터 초기화될 떄까지의 범위를 TDZ라고 말한다.
class Person {}
// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
클래스는 반드시 new 연산자와 함께 호출해야 한다.
class Person {}
// 클래스를 new 연산자 없이 호출하면 타입 에러가 발생한다.
const me = Person();
// TypeError: Class constructor Person cannot be invoked without 'new'
그렇지 않으면 타입에러가 발생한다.
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
}
constructor는 인스턴스를 생성하고 초기화 하기위한 특수한 메서드이다. 이름을 변경할 수 없다.
class Person {
constructor() {}
constructor() {}
}
// SyntaxError: A class may only have one constructor
constructor는 클래스 내에 최대 한 개만 존재할 수 있다. 2개 이상이면 문법 에러가 발생한다.
class Person {
// constructor를 생략하면 다음과 같이 빈 constructor가 암묵적으로 정의된다.
constructor() {}
}
// 빈 객체가 생성된다.
const me = new Person();
console.log(me); // Person {}
constructor를 생략하면 암묵적으로 정의되고 클래스는 빈 객체를 생성한다.
인스턴스를 초기화 하려면 constructor를 생략해서는 안된다.
class Person {
constructor(name) {
this.name = name;
// 명시적으로 객체를 반환하면 암묵적인 this 반환이 무시된다.
return {};
}
}
// constructor에서 명시적으로 반환한 빈 객체가 반환된다.
const me = new Person('Lee');
console.log(me); // {}
만약 다른 객체를 반환하면 return문에 명시한 객체가 반환된다.
class Person {
constructor(name) {
this.name = name;
// 명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환된다.
return 100;
}
}
const me = new Person('Lee');
console.log(me); // Person { name: "Lee" }
명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환된다.
constructor 내부에서 return문은 반드시 생략해야한다
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
}
const me = new Person('Lee');
me.sayHi(); // Hi! My name is Lee
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 정적 메서드
static sayHi() {
console.log('Hi!');
}
}
// 인스턴스 생성
const me = new Person('Lee');
me.sayHi(); // TypeError: me.sayHi is not a function
정적 메서드는 인스턴스로 호출할 수 없다. 인스턴스의 프로토탕비 ㅁ체인에 존재하지 않기 떄문이다. 즉 인스턴스로 클래스의 메서드를 상속 받을 수 없다.
class Square {
// 정적 메서드
static area(width, height) {
return width * height;
}
}
console.log(Square.area(10, 10)); // 100
class Square {
constructor(width, height) {
this.width = width;
this.height = height;
}
// 프로토타입 메서드
area() {
return this.width * this.height;
}
}
const square = new Square(10, 10);
console.log(square.area()); // 100
만약 인스턴ㄴ스 프로퍼티를 참조해야한다면 정적 메서드 대신 프로토타입 메서드를 사용해야 한다.
class Person {
// 생성자
constructor(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // Person {}
console.log(Object.getPrototypeOf(this) === Person.prototype); // true
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.name = name;
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}
}
class Person {
constructor(name) {
// 인스턴스 프로퍼티
this.name = name; // name 프로퍼티는 public하다.
}
}
const me = new Person('Lee');
// name은 public하다.
console.log(me.name); // Lee
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
}
const me = new Person('Ungmo', 'Lee');
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(`${me.firstName} ${me.lastName}`); // Ungmo Lee
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
me.fullName = 'Heegun Lee';
console.log(me); // {firstName: "Heegun", lastName: "Lee"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(me.fullName); // Heegun Lee
// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
console.log(Object.getOwnPropertyDescriptor(Person.prototype, 'fullName'));
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}
getter: 인스턴스 프로퍼티에 접근할 때 마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용한다
setter: 인스턴스 프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용한다.
getter는 무언가 취득할 때 사용하므로 반드시 반환해야하고 setter는 무언가를 프로퍼티에 할당해야 할 때 사용하므로 반드시 매개변수가 있어야한다.
class Person {
this.name = ''; // SyntaxError: Unexpected token '.'
}
클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드를 바인딩해서는 안 된다. this는 클래스의 constructor와 메서드 내에서만 유효하다.
class Person {
name;
}
const me = new Person();
console.log(me); // Person {name: undefined}
클래스 필드를 초기화하지 않으면 undefined를 갖는다.
인스턴스를 생성할 때 외부의 초기값으로 클래스 필드를 초기화 해야한다면 constructor에서 클래스 필드를 초기화 해야한다.
class Person {
// 클래스 필드에 문자열을 할당
name = 'Lee';
// 클래스 필드에 함수를 할당
getName = function () {
return this.name;
}
// 화살표 함수로 정의할 수도 있다.
// getName = () => this.name;
}
const me = new Person();
console.log(me); // Person {name: "Lee", getName: ƒ}
console.log(me.getName()); // Lee
클래스 필드에 할당하는 경우 프로토타입 메서드가 아닌 인스턴스 메서드가 된다.
class Person {
// private 필드 정의
#name = '';
constructor(name) {
// private 필드 참조
this.#name = name;
}
}
const me = new Person('Lee');
console.log(me.#name);
// SyntaxError: Private field '#name' must be declared in an enclosing class
private 필드 #name은 클래스 외부에서 참조할 수 없다.
class Person {
// private 필드 정의
#name = '';
constructor(name) {
this.#name = name;
}
// name은 접근자 프로퍼티다.
get name() {
// private 필드를 참조하여 trim한 다음 반환한다.
return this.#name.trim();
}
}
const me = new Person(' Lee ');
console.log(me.name); // Lee
class Person {
constructor(name) {
// private 필드는 클래스 몸체에서 정의해야 한다.
this.#name = name;
// SyntaxError: Private field '#name' must be declared in an enclosing class
}
}
class Animal {
constructor(age, weight) {
this.age = age;
this.weight = weight;
}
eat() { return 'eat'; }
move() { return 'move'; }
}
// 상속을 통해 Animal 클래스를 확장한 Bird 클래스
class Bird extends Animal {
fly() { return 'fly'; }
}
const bird = new Bird(1, 5);
console.log(bird); // Bird {age: 1, weight: 5}
console.log(bird instanceof Bird); // true
console.log(bird instanceof Animal); // true
console.log(bird.eat()); // eat
console.log(bird.move()); // move
console.log(bird.fly()); // fly
// 수퍼(베이스/부모)클래스
class Base {}
// 서브(파생/자식)클래스
class Derived extends Base {}
서브클래스: 상속을 통해 확장된 클래스
수퍼클래스: 서브클래스에게 상속된 클래스
extends의 역할은 수퍼클래스와 서브클래스 간의 상속 관계를 설정하는 것이다. 클래스도 프로토타입을 통해 상속 관계를 구현한다.
// 생성자 함수
function Base(a) {
this.a = a;
}
// 생성자 함수를 상속받는 서브클래스
class Derived extends Base {}
const derived = new Derived(1);
console.log(derived); // Derived {a: 1}
// 수퍼클래스
class Base {
constructor(a, b) { // ④
this.a = a;
this.b = b;
}
}
// 서브클래스
class Derived extends Base {
constructor(a, b, c) { // ②
super(a, b); // ③
this.c = c;
}
}
const derived = new Derived(1, 2, 3); // ①
console.log(derived); // Derived {a: 1, b: 2, c: 3}
서브 클래스에서 constructor를 생햑하지 않는 경우 서브클래승의 constructor에서는 반드시 super 호출해야한다.
서브 클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다.
super는 반드시 서브클래스의 constructor에서만 호출한다.