- 클래스는 함수다.
- 클래스는 일급 객체다.
- 클래스는 호이스팅이 발생하지 않는 것 처럼 보이지만, let이나 const 키워드로 선언한 변수처럼 호이스팅된다.
- 클래스는 인스턴스를 생성하는 생성자 함수다.(new와 반드시 함께)
JavaScript는 프로토 타입 기반 객체지향 언어
-> prototype 기반 객체지향 언어는 Class가 필요 없음
-> 근데 Class에 익숙한 프로그래머들은 혼란을 느껴 ES6에서 도입
- 클래스를 객체 생성 패턴의 문법적 설탕 보단 새로운 객체 생성 메커니즘으로 보는 것이 더 합당
- 클래스는 생성자 함수와 매우 유사하지만 아래 표와 같은 차이가 있음
클래스 | 생성자 | |
---|---|---|
new 연산자 없이 호출 | error | 일반 함수로 호출 |
extends, super 키워드 | 제공 O | 제공 X |
호이스팅 | 발생하지 않는 것 처럼 보임 | 함수 선언문 생성자 함수는 함수 호이스팅, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅 |
strict mode | 암묵적으로 지정되어 해제 불가능 | 암묵적으로 지정 X |
Enumerable(열거) | false | ture |
- 기본 형태 : 파스칼 케이스
- 몸체에서 정의 가능한 메서드는 아래 3개 뿐!(자세한 이야기는 25.5에)
- constructor
- prototype 메서드
- static 메서드
- 클래스는 함수다라는 말은 일급 객체라는 말
- 무명의 리터럴 생성, 변수나 자료구조에 저장, 전달, 반환 값 등으로 사용 가능
// 기본 형태 : 일반적으로 파스칼 케이스
class Person{}; // 익명 클래스 표현식
class Person = class MyClass{}; // 기명 클래스 표현식
// 클래스 몸체에서 정의 가능한 메서드는
// 1. constructor(생성자) 메서드
// 2. prototype 메서드
// 3. static 메서드
class Person {
// 1 constructor 메서드
constructor(name){
this.name = name; // 인스턴스 초기화.
}
// 2 prototype 메서드
sayHi(){
console.log(`Hi! My name is ${this.name}`);
}
// 3 static 메서드
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!
- 클래스는 호이스팅이 발생하지 않는 것 처럼 보이지만, let이나 const 키워드로 선언한 변수처럼 호이스팅 됨
const Person = "";
{
console.log(Person); // ReferenceError :...
// -> 호이스팅이 발생하지 않는다면 위에서 ''으로 설정하였으니 ''이ㅣ 나와야 함
// -> 런타임 이전에 선언되었지만 클래스 선언문 이전에 일시적 사각지대에 빠져서 이렇게 보이는 것
class Person {}
}
- 클래스는 인스턴스를 생성하는 생성자 함수
- 인스턴스 생성이 유일하므로 반드시 new와 함께 호출해야 함!
const me = class Person {};
class Person {}
const mine = new Person(); // O
// const mine = class Person(); // X. error
const you = class MyClass {};
console.log(MyClass); // X. error 외부 접근 불가
메서드는 3가지만 가능
- constructor
- prototype 메서드
- static 메서드
+) 프로퍼티도 정의 가능할수도..! (자세한 이야기는 25.7에서)
인스턴스 생성, 초기화 메서드 이름 변경 불가
클래스는 함수 -> 자신만의 스코프가 있다(함수 스코프처럼)
constructor는 메서드로 해석 X 클래스가 생성한 함수 객체 코드의 일부
constructor와 생성자 함수의 차이
1.클래스에 최대 1개만 존재 가능. 2개 이상은 에러 2.생략가능
class Person{ // 이렇게 2개 쓰면 에러
constructor() { ... }
constructor() { ... }
}
class Person{} // 이렇게 constructor 생략 가능. 암묵적으로 빈 객체 생성
// 초기화
// 1.
class Person{
constructor(){
this.name = 'Lee';
this.address = 'Seoul';
}
}
const me = new Person(); // 인스턴스 프로퍼티가 추가
// 2.
class Person{
constructor(name, address){
this.name = name;
this.address = address;
}
}
const me = new Person('Lee', 'Seoul'); // 매개변수로 초기값 전달
인스턴스 초기화를 위해 constructor를 생략하지 않는 것이 좋다. construct는 this를 암묵적으로 반환하기 때문에 별도의 반환문이 있으면 안된다.(기본 동작 훼손. 반환문 설정한 경우 반환문의 내용이 return)
클래스 몸체에서 정의한 메서드는, 생성자 함수에 의한 객체 생성 방식과는 다르게 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 됨
prototype 체인도 동일하게 적용됨
// 1 생성자라면~
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function () {
console.log(`Hi My name is ${this.name}`);
};
// 2 prototype 메서드라면~
class Person {
constructor(name) {
this.name = name;
}
// 요렇게 별도로 하지 않아도 prototype이 된다.
sayHi() {
console.log(`Hi My name is ${name}`);
}
}
정적 메서드란? 인스턴스를 생성하지 않아도 호출 가능한 메서드
// 1 생성자라면~
function Person(name) {
this.name = name;
}
Person.sayHi = function () {
console.log("Hi!");
};
Persion.sayHi(); // Hi!
// 2 static 메서드라면~
class Person {
constructor(name) {
this.name = name;
}
// 이렇게 앞에 static
static sayHi() {
console.log("Hi!");
}
}
정적 메서드는 클래스로 호출 O 인스턴스로 호출 X
Persion.sayHi(); // 클래스로 호출 O
const me = new Person("Lee");
me.sayHi(); // 인스턴스로 호출 X
- function 키워드를 생략한 메서드 축약 표현 사용
- 객체 리터럴과는 다르게 클래스에 메서드를 정의할 때는 콤마 필요 X
- 암묵적으로 strict mode로 실행
- for...in 문이나 Object.keys 메서드 등으로 열거 불가.
- new 연산자와 함께 호출 X(non-constructor)
class Person {
// 생성자
constructor(name) {
// 1. 인스턴스 생성과 this 바인딩
// constructor 내부 코드 실행 전 암묵적으로 빈 객체 생성 -> 인스턴스 생성
console.log(this); // Person{}. 인스턴스는 this에 바인딩
console.log(Object.getProtorypeOf(this) === Person.prototype); // true. 인스턴스의 prototype으로 클래스의 prototype 프로퍼티가 가리키는 객체
// 2. 인스턴스 초기화
// constructor 내부 코드가 실행되어 this에 바인딩 되어있는 인스턴스 초기화
// 만약 constructor가 생략되었다면 이 과정도 생략
this.name = name;
// 3. 인스턴스 반환
// 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 임묵적으로 반환
}
}
- 인스턴스 프로퍼티는 constructor 내부에서 정의해야 함
- constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 됨
- 항상 public. 별도의 private, public, protected 키워드와 같은 접근 제한자는 지원 X
class Person {
constructor(name) {
// 인스턴스 프로퍼티
// 25.6 클래스 인스턴스 생성 과정의 내용대로 암묵적인 빈 객체(인스턴스)에 프로퍼티가 추가되어 초기화
this.name = name;
}
}
const me = new Person('Lee');
console.log(me); // Person {name: "Lee"}
console.log(me.name); // Lee
접근자 프로퍼티란? 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수(getter, setter)로 구성된 프로퍼티
- getter : 접근할 때마다 프로퍼티 값을 조작 or 별도의 행위가 필요할 때 사용. 반드시 무언가를 반환 -> 매개변수 필요
- setter : 할당할 떄마다 프로퍼티 값을 조작 or 별도의 행위가 필요할 때 사용. 단 하나의 값만 할당 -> 단 하나의 매개변수 선언 가능
- 클래스의 메서드는 기본적으로 프로토타입 메서드.
- 클래스의 접근자 프로퍼티 또한 인스턴스 프로퍼티가 아닌 프로토타입의 프로퍼티.
const person = {
firstName: 'Nuri',
lastName: 'Lee',
// 접근자 프로퍼티 : getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// 접근자 프로퍼티 : setter
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
};
// 데이터 프로퍼티
console.log(`${person.firstName} ${person.lastName}`); // Nuri Lee
// 접근자 프로퍼티 - setter
person.fullName = 'Nuri Lee';
console.log(person); // {firstName: "Nuri", lastName: "Lee"}
// 접근자 프로퍼티 - getter
console.log(person.fullName); // Nuri Lee
클래스 필드란? 클래스 필드는 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
- 인스턴스 생성시
- 외부 초기값으로 클래스 필드를 초기화할 필요가 O -> constructor에서 인스턴스 프로퍼티를 정의하는 기존 방식 사용
- 외부 초기값으로 클래스 필드를 초기화할 필요가 X -> constructor에서 인스턴스 프로퍼티를 정의하는 기존 방식, 클래스 필드 정의 제안 방식 사용
- 인스턴스 프로퍼티는 인스턴스를 통해 클래스 외부에서 언제나 참조 가능한 public
- 2021년 private 필드 정의 가능한 새로운 표준 사양이 제안되어 사용 가능
class Person {
// name = ''; // public
#name = ''; // private
constructor(name) {
this.#name = name;
}
get name() {
return this.#name.trim(); // 이런 식으로 private 필드를 return하여 접근 가능
}
}
- 2021년 static 필드 정의 가능한 새로운 표준사양이 제안되어 사용 가능
class Person {
// name = ''; // public
// #name = ''; // private
static PI - 22/7; // static
static #name = ''; // static, private
...
}
- 상속에 의한 클래스 확장은 코드 재사용 관점에서 매우 유용
- 상속받은 클래스의 속성을 그대로 사용하고 자신만의 고유한 속성을 추가하여 확장하는 방식
extends
키워드(아래 2) extends 키워드)
- 생성자 함수는 상속 문법 제공 X
class Animal{} // 수퍼클래스
class Lion extends Animal{} // 서브클래스
- 클래스 상속 키워드
- 수퍼클래스, 서브클래스 간의 상속 관계 설정
- 서브클래스 : 상속을 통해 확장된 클래스 (파생 클래스, 자식 클래스)
- 수퍼클래스 : 서브클래스에게 상속된 클래스 (베이스 클래스, 부모 클래스)
- 클래스뿐 아니라 생성자 함수, 함수 객체로 평가 가능한 모든 표현식 상속 가능
function Animal(a) {
this.a = a;
}
class Lion extends Animal {}
function Animal1() {}
class Animal2 {}
class Bird extends (condition ? Animal1 : Animal2) {}
- 서브클래스에 constructor를 생략하더라도 new 연산자와 함께 클래스를 호출할 때 빈 객체(인스턴스)가 자동으로 생성
- 프로퍼티를 가진 인스턴스를 생성하려면 consturctor를 생략하지 않고 내부에 프로퍼티를 추가해야 함
super 키워드란? 함수처럼 호출도, this 같이 식별자처럼 참조도 가능한 특수한 키워드
- super를 호출하면? 수퍼클래스의 constructor를 호출
- super를 참조하면? 수퍼클래스의 메소드 호출 가능
super를 호출하면? 수퍼클래스의 constructor를 호출
// 수퍼클래스
class Animal {
constructor(a, b) { // 4.
this.a = a;
this.b = b;
}
}
// 서브클래스
class Lion extends Animal {
constructor(a,b,c) { // 2. constructor 실행
super(a,b); // 3. super 호출되며 수퍼클래스의 constructor인 4에 전달
this.c = c;
}
}
const lion = new Lion(1,2,3); // 1. new 연산자와 함께 클래스 호출하며 2에 전달
console.log(lion);
// 결론 : 상속 관계의 두 클래스는 서로 협력하며 인스턴스 생성
주의사항
- 서브클래스에서 constructor를 선언한다면 반드시 super를 호출해야 함
- 서브클래스의 constructor에서만 super 호출 가능
- 서브클래스의 constructor에서 super 호출 후 this 참조 가능
super를 참조하면? 수퍼클래스의 메소드 호출 가능
- 서브클래스의 프로토타입 메서드 내에서 super는 수퍼클래스의 프로토타입 메서드를 가리킨다.
- ES6의 메서드 축약 표현으로 정의된 함수만이 super 참조 가능
- 서브클래스의 정적 메서드 내에서 super는 수퍼클래스의 정적 메서드를 가리킨다.
// 수퍼클래스
class Animal {
constructor(a, b) { // 4.
this.a = a;
this.b = b;
}
sayHi() {
return `Hi! ${this.a}`;
}
static sayHello() {
return `Hello! ${this.a}`;
}
}
// 서브클래스
class Lion extends Animal {
sayHi(){
return `${super.sayHi()}`; // Animal의 프로토타입 메서드 sayHi
}
static sayHello(){
return `${super.sayHello()}`; // Animal의 정적 메서드 sayHello
}
}
// 수퍼클래스
class Rectangle { // 2. 수퍼클래스의 인스턴스 생성과 this 바인딩
constructure(width, height){ // 3. 수퍼클래스의 인스턴스 초기화
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
toString() {
return `width = ${this.width}, height = ${this.height}`;
}
}
// 서브클래스
class ColorRectangle extends Rectangle { // 4. 서브클래스 constructor로의 복귀와 this 바인딩
constructor(width, height, color) {
super(width, height); // 1. 서브클래스의 super 호출
this.color = color; // 5. 서브클래스의 인스턴스 초기화
// 6. 인스턴스 반환
}
toString(){
return super.toString() + `, color = ${this.color}`;
}
}
const colorRectangle = new ColorRectangle(2, 4, 'red');
console.log(colorRectangle);
console.log(colorRectangle.getArea());
console.log(colorRectangle.toString());
- 반드시 서브클래스의 constructor에서 반드시 super를 호출해 수퍼클래스의 constructor를 호출해야 함
- new 연산자 호출의 경우
- 수퍼클래스 : 암묵적으로 빈 객체(인스턴스) 생성 후 this에 바인딩
- 서브클래스 : 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임 -> 본인이 하지 않으니 위임하기 위해 super 호출 필요
- constructor로 생성된 인스턴스는 수퍼클래스, new 연산자로 호출된 클래스는 서브클래스 -> 인스턴스는 서브클래스가 생성한 것으로 처리
- 수퍼클래스의 construtor가 실행되어 this에 바인딩되어 있는 인스턴스 초기화
- super 호출 종료후 제어 흐름이 서브클래스로
- super가 반환한 인스턴스가 this에 바인딩 되므로, 서브클래스는 별도의 인스턴스를 생성하지 않고 반환한 인스턴스를 this에 그대로 바인딩하여 사용
- super 호출 이후, 서브클래스의 construtor 인스턴스 초기화 실행
- 클래스의 모드느 처리 완료 후 완성된 인스턴스가 바인딩 된 this가 암묵적으로 반환
- 빌트인 생성자 함수도 extends 키워드를 사용해 확장 가능
class MyArray extends Array {
uniq(){
return this.filter((v, i, self) => self.indexOf(x) === i);
}
average() {
return this.reduce((pre, cur) => pre + cur, 0) / this.length;
}
}
const myArray = new MyArray(1,1,2,3);
console.log(myArray); // MyArray(4) [1,1,2,3]
console.log(myArray.uniq()); // MyArray(3) [1,2,3]
console.log(myArray.average()); // 1.75