JS

Class(extends, super)

Hyeon_E 2024. 6. 12. 16:03

클래스(class)

원하는 구조의 객체 틀을 짜놓고, 비슷한 모양의 객체를 공장처럼 찍어낼 수 잇음

쉽게 생각해서 클래스 = 붕어빵 기계 그리고 객체 = 붕어빵으로 보면 됨

 

▶ 클래스를 사용하는 이유

자바스크립트프로토타입(prototype) 기반의 객체지향 프로그래밍 언어

ES6부터 추가된 클래스는 직관적으로 쉽게 코드를 읽을 수 있게 만들어 줄 뿐만 아니라 작성하기도 쉽고

class 기반 언어에 익숙한 개발자가 더 빠르게 적응할 수 있음

 

prototype

function Me(name) {
  this.name = name;
}

Me.prototype.wow = function () {
  console.log("WOW!");
};

let person = new Me("Jason");
person.wow(); // WOW!

 

class

class Me {
  constructor(name){
    this.name = name;
  }
  
  wow(){
    console.log("WOW!");
  }
}
  
let person = new Me("Jason");
person.wow() // WOW!

 

프로토타입 문법과 클래스 문법은 같은 결과를 출력하며

문법 생김새만 다르고 내부 로직은 완전히 같은 구조(프로토타입 기반의 코드)

 

▶ 클래스 문법

Constructor

constructor는 클래스 내부에서 인스턴스 객체를 초기화하는 특수한 메소드
클래스 내에 한 개만 존재할 수 있으며, 만약 명시적으로 작성하지 않으면, 암시적으로 빈 constructor가 생성
따라서 초기화할 내용이 없다면, constructor를 작성하지 않아도 됨

 

class User {
  constructor(name) {
    this.name = name
  }
  greet() {
    return `Hello, I'm ${this.name}.`
  }
}

const heropy = new User('Heropy')
console.log(heropy.greet()) // 'Hello, I'm Heropy.'

const neo = new User('Neo')
console.log(neo.greet()) // 'Hello, I'm Neo.'

console.log(heropy.greet === neo.greet) // true

 

게터(Getter)

get 키워드를 통해, 특정한 속성의 값을 얻거나 속성을 계산해 반환하는 메소드를 정의할 수 있음
이렇게 정의한 메소드를 게터(Getter)라고 부르며, 계산된 속성(Computed property)으로 이해할 수 있음
게터는 함수지만, 호출하지 않고 속성처럼 사용

 

class Counter {
  constructor(initialValue = 1) {
    this.value = initialValue
  }
  get double() {
    return this.value * 2
  }
  increase() {
    this.value += 1
  }
}

const counter = new Counter(1)
console.log(counter.value) // 1
console.log(counter.double) // 2

counter.increase()
console.log(counter.value) // 2
console.log(counter.double) // 4

 

세터(Setter)

set 키워드를 통해, 게터에 값을 할당했을 때 호출하는 메소드를 정의할 수 있음
이렇게 정의한 메소드를 세터(Setter)라고 부릅니다.

 

class Counter {
  constructor(initialValue = 1) {
    this.value = initialValue
  }
  get double() {
    return this.value * 2
  }
  set double(newValue) {
    this.value = newValue / 2
  }
  increase() {
    this.value += 1
  }
}

const counter = new Counter(1)
console.log(counter.value) // 1
console.log(counter.double) // 2

counter.increase()
console.log(counter.value) // 2
console.log(counter.double) // 4

counter.double = 10
console.log(counter.value) // 5
console.log(counter.double) // 10


double 속성(게터)은 value 속성을 기준으로 값을 계산해 반환함
따라서 double에 값을 할당하면, 기준이 되는 value 속성의 값을 갱신해야 게터의 반환값도 변경될 수 있음

 

- Getter/Setter

바로 접근하지 말고 메서드를 통해 경유해서 설정하도록 하는 기법이 바로 Getter와 Setter 개념이라고 보면 됨

Getter와 Setter를 사용하면 객체 내부 속성에 직접 접근하지 않아 객체의 정보 은닉을 가능하게 해주어 보안을 강화할 수 있고 코드의 안전성과 유지보수성을 높일 수 있으며 옳지 못한 값을 넣으려고 할때 이를 미연에 방지할 수 있다는 장점이 있음

 

정적 메소드

static 키워드를 통해, 클래스의 인스턴스 없이 호출하는 메소드를 정의
이렇게 정의한 메소드를 정적 메소드(Static method)라고 부름
주로, 클래스 단위로 사용하는 유틸리티(Utilities) 함수를 정의할 때 사용

 

class User {
  constructor(name, age) {
    this.name = name
    this.age = age 
  }
  greet() {
    return `Hello, I'm ${this.name}.`
  }
  static isUser(value) {
    return !!(value && typeof value.name === 'string' && typeof value.age === 'number')
  }
}

const lewis = new User('Lewis', 92)
console.log(User.isUser(lewis)) // true

const neo = {
  name: 'Neo',
  age: 12
}
console.log(User.isUser(neo)) // true

const amy = {
  name: 'Amy'
}
console.log(User.isUser(amy)) // false

 

대표적으로 배열 데이터에서 사용하는 Array.isArray() 메소드도 정적 메소드
배열 데이터인지 확인하는 메소드로, Array 인스턴스(배열 데이터)가 아닌 Array 클래스 자체에서 호출

 

const arr = [1, 2, 3]
const obj = { a: 1 }

console.log(Array.isArray(arr)) // true
console.log(Array.isArray(obj)) // false

 

화살표 함수 메소

화살표 함수(Arrow function)를 통해, 메소드를 정의할 수 있음
주의할 점은 화살표 함수 메소드는 prototype이 아닌 생성된 각 인스턴스의 개별 메소드로 추가
즉, 화살표 함수 메소드는 프로토타입 메소드가 아님

 

class User {
  // ...
  greet = () => {
    return `Hello, I'm ${this.name}.`
  }
}

// ...
console.log(neo.greet === evan.greet) // false

 

greet가 각 인스턴스의 개별 메소드로 추가되면서 서로 일치하지 않는 것을 확인할 수 있음

 

정보 은닉

클래스 내부에서 # 키워드를 통해 특정한 속성이나 메소드를 클래스 외부에서 접근할 수 없도록 제한할 수 있으며,

이를 비공개 필드(Private field)라고 부름

 

class 클래스 {
  // 클래스 바디
}
class 클래스 {
  #속성 = 초깃값
  #메소드() {}
}

 

비공개 필드는 클래스 바디(Class body)에서 정의

 

class User {
  #age = 0
  constructor(name, age) {
    this.name = name
    this.#age = age
  }
  getAge() {
    return this.#age
  }
}
class Admin extends User {
  constructor(name, age) {
    super(name, age)
    console.log(this.#age) // Error: Private field '#age' must be declared in an enclosing class
  }
}

const neo = new User('Neo', 12)
console.log(neo.name) // 'Neo'
console.log(neo.getAge()) // 12
console.log(neo.#age) // Error: Private field '#age' must be declared in an enclosing class

 

#age 속성은 클래스 내부에서만 접근 가능함(자식 클래스에서도 접근할 수 없음)

 

익명 클래스

클래스 이름을 생략해, 익명 클래스(Anonymous class)로 정의할 수 있음
사용처에서 이름을 결정하는 모듈의 기본 내보내기(Default export) 같은 형태로 사용

 

export default class {
  constructor(name) {
    this.name = name
  }
}

 

import MyClass from './myClass.js'

const neo = new MyClass('Neo')
console.log(neo.name) // 'Neo'

 

클래스 상속(extends/super)

클래스 상속(class inheritance, subclassing) 기능을 통해 한 클래스의 기능을 다른 클래스에서 재사용할 수 있음

▶ extends

extends 키워드는 클래스를 다른 클래스의 하위 클래스로 만들기 위해 사용

 

class Parent {
  // ...
}

class Child extends Parent {
  // ...
}

 

 extends 키워드를 통해 Child 클래스가 Parent 클래스를 상속
이 관계를 보고 '부모 클래스-자식 클래스' 혹은 '슈퍼 클래스(superclass)-서브 클래스(subclass)' 관계라고 함

 

클래스 A가 다른 클래스 B를 상속받으면

  • 자식 클래스 A를 통해 부모 클래스 B의 정적 메소드와 정적 속성을 사용할 수 있음
  • 부모 클래스 B의 인스턴스 메소드와 인스턴스 속성을 자식 클래스 A의 인스턴스에서 사용할 수 있음

class Parent {
  static staticProp = 'staticProp';
  static staticMethod() {
    return 'I\'m a static method.';
  }
  instanceProp = 'instanceProp';
  instanceMethod() {
    return 'I\'m a instance method.';
  }
}

class Child extends Parent {}

// 상속하면 부모의 static요소들을 사용 가능
console.log(Child.staticProp); // staticProp
console.log(Child.staticMethod()); // I'm a static method.

// 상속하면 부모의 인스턴스를 사용 가능
const c = new Child();
console.log(c.instanceProp); // instanceProp
console.log(c.instanceMethod()); // I'm a instance method.

 

Child 클래스는 Parent 클래스의 정적 요소와 인스턴스 요소를 프로토타입 체인을 통해 상속받음

 

▶ super

super 키워드의 동작 방식

  • 생성자 내부에서 super를 함수처럼 호출하면, 부모 클래스의 생성자가 호출
  • 정적 메소드 내부에서는 super.prop과 같이 써서 부모 클래스의 prop 정적 속성에 접근할 수 있
  • 인스턴스 메소드 내부에서는 super.prop과 같이 써서 부모 클래스의 prop 인스턴스 속성에 접근할 수 있음
super(); // 부모 생성자
super.메소드명 // 접근

 

class Person{
    constructor(name, first, second){
        this.name=name;
        this.first=first;
        this.second=second;
    }

    sum(){
        return (this.first + this.second);
    }
} 

class Person2 extends Person{
	// override Person
    constructor(name, first, second, third){
        super(name, first, second); //부모 생성자를 가져와서 행하게 함
        this.third = third;
    }
    
    sum(){
        // 오버로딩 메소드에서 온전한 부모 메소드를 사용하고 싶을때
        return super.sum() + this.third;  // 부모 메소드를 가져와서 사용
    }
}

var kim = new Person2('kim', 10, 20, 30);
document.write(kim.sum()); // 60

 

super 함수는 자식 클래스의 this 키워드 사용 전에 호출해야함

 

▶ 인스턴스 확인

instanceof 키워드를 통해, 어떤 클래스의 인스턴스인지를 포함해 그 클래스의 상속 체인까지 확인할 수 있음
혹은, 인스턴스의 .contructor 속성으로 특정 클래스의 인스턴스인지 확인할 수 있음

 

class User {
  constructor(name) {
    this.name = name
  }
}
class Admin extends User {
  constructor(name) {
    super(name)
    this.admin = true
  }
}
const neo = new Admin('Neo')

console.log(neo instanceof Admin) // true
console.log(neo instanceof User) // true
console.log(neo instanceof Object) // true

console.log(neo.constructor === Admin) // true
console.log(neo.constructor === User) // false
console.log(neo.constructor === Object) // false

console.log(neo)

 

 

Reference 

자바스크립트 클래스 문법 - 완벽 가이드

[JavaScript] class란 무엇인가?

JS 클래스 핵심 패턴