Front-end Developer

0%

클래스 / Class

ES6에 새로 도입되었으면 프로토타입 기반 객체지향 모델의 syntactic sugar라고 할 수 있다. 클래스는 생성자 함수와 비슷하게 동작하지만 보다 엄격하다. 일종의 새로운 객체 생성 매커니즘 이라고 할 수 있다.

클래스와 생성자 함수의 차이점

  1. 클래스는 new연산자 없이 호출하면 에러가 발생한다.
  2. 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다.
  3. 클래스는 let, const처럼 호이스팅이 발생하지 않는 것처럼 동작한다. 생성자 함수의 경우 함수 선언문으로 정의되었으면 함수 호이스팅, 함수 표현식으로 정의되었으면 변수 호이스팅이 발생한다.
  4. 클래스 내의 모든 코드는 암묵적으로 strict 모드가 지정되어 실행된다.
  5. 클래스의 constructor, 프로토타입 메소드, 정적 메소드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이다. 다시 말해, 열거되지 않는다.

클래스 정의

  1. 파스칼 케이스를 사용한다.(사용하지 않아도 에러가 발생하지는 않음.)
  2. 표현식으로 정의할 수 있고, 기명 또는 익명으로 정의할 수 있다. 이는 클래스가 일급 객체라는 것을 의미한다.
  3. 클래스 몸체에는 0개 이상의 메소드만 정의할 수 있다. 메소드의 종류는 constructor(생성자), 프로토타입 메소드, 정적 메소드 3가지가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 클래스 선언문
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. 클래스 정의 이전에 참조할 수 없다.
  2. 클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생한다. 단, 클래스는 let, const 키워드로 선언한 변수처럼 호이스팅된다. 따라서 클래스 선언문 이전에 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.

인스턴스 생성

클래스는 함수로 평가되므로 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성한다. new 연산자를 절대 생략하면 안된다.

1
2
3
4
5
6
class Person {}

// 인스턴스 생성
const me = new Person();

console.log(me); // Person {}

constructor

클래스는 인스턴스를 생성하기 위한 생성자 함수이며, constructor는 인스턴스를 생성하고 초기화하기 위한 특수한 메소드이다. 클래스 내에 한 개만 존재해야 하며, 생략도 가능하다 생략하여도 디폴트 constructor가 암묵적으로 정의된다.constructor는 이름을 변경할 수 없다. constructor 내부의 this는 클래스가 생성할 인스턴스를 가리킨다.

1
2
3
4
5
6
7
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
}

constructor 내에서는 인스턴스의 생성과 동시에 인스턴스 프로퍼티 추가를 통해 인스턴스의 초기화를 실행한다. 따라서 인스턴스를 초기화하려면 constructor를 생략해서는 안된다. 또한 별도의 반환문을 갖지 않아야 한다. 생성자 함수와 마찬가지로 암묵적으로 this를 반환하기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name) {
this.name = name;

// 명시적으로 원시 값을 반환하면 원시 타입의 반환은 무시되고 암묵적으로 this가 반환된다.
return 100;
}
}

const me = new Person('Lee');
console.log(me); // Person { name: "Lee" }

프로토타입 메소드

생성자 함수는 프로토타입 메소드를 생성하고자 할 때 명시적으로 프로토타입 메소드를 추가해야 한다. 클래스 몸체에서 정의한 메소드는 생성자 함수에 의한 객체 생성 방식과는 다르게 클래스의 prototype 프로퍼티에 메소드를 추가하지 않아도 기본적으로 프로토타입 메소드가 된다.

1
2
3
4
5
6
7
8
9
10
//생성자함수의 프로토타입 메소드
Person.prototype.sayHi = function () {
console.log(`Hi! My name is ${this.name}`);
};

//클래스의 프로토타입 메소드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
}

정적 메소드

정적 메소드는 인스턴스를 생성하지 않아도 호출할 수 있는 메소드를 말하는데, 생성자 함수의 경우 명시적으로 메소드를 추가해야 한다. 하지만 class에서는 메소드에 static 키워드를 붙이면 정적 메소드(클래스 메소드)가 된다. 정적 메소드는 클래스 자신의 메소드가 되며, 인스턴스를 생성하지 않아도 호출할 수 있다. 또한 인스턴스의 프로토타입 체인 상에 존재하지 않으므로 인스턴스로 호출할 수 없다.

1
2
3
4
5
6
7
8
9
10
// 생성자 함수의 정적 메소드
Person.sayHi = function () {
console.log('Hi!');
};

// 클래스의 정적 메소드
static sayHi() {
console.log('Hi!');
}
}

정적 메소드와 프로토타입 메소드의 차이

  1. 정적 메소드와 프로토타입 메소드는 소속해 있는 프로토타입 체인이 다르기 때문에 정적 메소드는 클래스로 호출, 프로토타입 메소드는 인스턴스로 호출한다.
  2. 정적 메소드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메소드는 인스턴스 프로퍼티를 참조할 수 있다.
  3. 정적 메소드와 프로토타입 메소드는 내부의 this바인딩이 다르다. this를 사용하지 않는 메소드는 정적 메소드, 인스턴스 프로퍼티를 참조할 필요가 있어 this를 사용해야 한다면 프로토타입 메소드로 정의한다.
  • 정적 메소드의 내부는 클래스를 가리킨다.
  • 프로토타입 메소드의 내부는 메소드를 호출한 객체, 즉 메소드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체에 바인딩된다.

클래스에서 정의한 메소드의 특징

  1. function 키워드를 생략한 메소드 축약 표현을 사용한다.
  2. 객체 리터럴과는 다르게 클래스에 메소드를 정의할 때는 콤마가 필요 없다.
  3. 암묵적으로 strict 모드로 실행된다.
  4. for…in 문이나 Object.keys 메소드 등으로 열거할 수 없다. 즉, 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이다.
  5. 내부 메소드 [[Construct]]를 갖지 않는 non-constructor이다. 따라서 new 연산자와 함께 호출할 수 없다.

클래스 필드 정의 제안

클래스 필드(필드 또는 멤버)는 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어이다. 자바스크립트는 인스턴스 프로퍼티를 선언하고 초기화하려면 반드시 생성자 함수 몸체 또는 클래스의 constructor 내부에서 this에 프로퍼티를 추가해야 한다. 인스턴스 프로퍼티를 참조할 때도 this를 사용해서 참조한다. 또한 클래스 몸체에는 메소만 선언할 수 있으므로 클래스 필드를 선언하면 에러가 발생한다. 클래스 필드에 초기값을 할당하지 않으면 undefined가 된다. 만약 외부의 초기값으로 클래스 필드를 초기화해야 한다면 constructor에서 초기화한다.

constructor 내부에서 this를 통해 정의한 인스턴스 프로퍼티는 인스턴스를 통해 클래스 외부에서 언제나 참조할 수 있다. 즉, 언제나 public이다.

상속에 의한 클래스 확장