[ES6] Class 사용하기(1) - 사용법과 prototype

권준혁·2020년 11월 1일
0

javascript

목록 보기
13/19
post-thumbnail

안녕하세요.
이번 포스팅에서는 자바스크립트의 Class에 대해서 살펴보겠습니다.

기존의 자바스크립트에서는 prototype을 이용해 유사하게 상속을 구현했었습니다. ES6이후 Class가 나오면서 명료하고 간편하게 객체를 생성하고, 상속을 구현할 수 있습니다.
내부적으로 class는 기존의 prototype을 연결하는 속기법일 뿐입니다.
인스턴스가 생성되면 속성과 메서드가 복제되는 것이 아닌, prototype에 대한 연결을 생성합니다.

코드부터 살펴보겠습니다.

지금부터 만드는 코드는 Coupon이라는 클래스로, 만료기한과 가격이라는 속성이 있으며 해당 클래스를 상속하는 다른 쿠폰클래스를 만들기도 합니다.


1. 클래스 사용법

1. new 키워드를 이용한 Class생성

class Coupon {
}
const coupon = new Coupon()

coupon에 Coupon클래스의 인스턴스를 생성했습니다.
함수처럼 표현식,선언식이 있지만 그런 부분은 제쳐두겠습니다.


2. constructor() 생성자함수로 속성설정

class Coupon {
    constructor (price, expiration) {
        this.price = price;
        this.expiration = expiration || '2주'
    }
}
const coupon = new Coupon(5)
console.log(coupon.price)            // 5
console.log(coupon['expiration'])    // 2주

생성자 메서드 constructor()는 function 키워드 없이 작성합니다.
이 메서드는 this문맥을 생성하기 때문에 this에 속성을 할당하며
인수를 이용해 인스턴스마다 다른 속성을 부여할 수 있습니다.


3. 메서드 정의하기

class Coupon {
    constructor (price, expiration) {
        this.price = price;
        this.expiration = expiration || '2주'
    }
    getPriceText() {
        return `$${this.price}`
    }
    getExpirationMessage() {
        return `이 쿠폰은 ${this.expiration} 후에 만료됩니다.`
    }
}
const coupon = new Coupon(5)
console.log(coupon.getPriceText())            
console.log(coupon.getExpirationMessage())

//result
$5
이 쿠폰은 2주 후에 만료됩니다.

메서드를 정의하려면 생성자 메서드와 마찬가지로 function키워드 없이 작성합니다. this로 현재 클래스의 속성에 접근할 수 있습니다.


4. 상속

할인율이 높고 기간이 짧은 FlashCoupon클래스를 상속을 통해 구현해보겠습니다.

class FlashCoupon extends Coupon {
    constructor(price, expiration) {
        super(price)    // 부모클래스 생성자 호출
        this.expiration = expiration || '2시간'    // 속성 덮어씌움
    }
    getExpirationMessage () {    // 동일이름 메서드 대체
        return `이 쿠폰은 깜짝쿠폰이며 ${this.expiration}후에 만료됩니다.`
    }
}
const flash = new FlashCoupon(10);
console.log(flash.price)    // 10
console.log(flash.getPriceText()) // $10
console.log(flash.getExpirationMessage())
// "이 쿠폰은 2시간 후에 만료됩니다.

super() 메서드는 부모클래스의 생성자를 호출합니다.
반드시 super()을 호출한 뒤에 새로운 속성을 추가하거나 부모속성을 덮어 씌울 수 있습니다.
getExpirationMessage ()를 덮어 씌웠습니다.
속성의 스코프체인은 자식컴포넌트 => 부모컴포넌트가 됩니다.
Coupon클래스의 getExpirationMessage() 메소드는 여전히 존재합니다. flash.getExpirationMessage()를 호출했을 때 FlashCoupon클래스에서 먼저 메소드를 찾습니다. 없을 경우에 부모클래스나 프로토타입을 확인하게 됩니다.
결과적으로 FlashCoupon클래스의 getExpirationMessage()메소드가 호출됩니다.


5. 부모메서드 호출하기

수정된 부모클래스 Coupon을 먼저 보겠습니다.

class Coupon {
    constructor (price, expiration) {
        this.price = price;
        this.expiration = expiration || '2주'
    }
    getPriceText () {
        return `$${this.price}`
    }
    getExpirationMessage () {
        return `이 쿠폰은 ${this.expiration}후에 만료됩니다.`
    }
    isRewardsEligible (user) {            // **
        return user.rewardEligible && user.active;
    }
    getReward (user) {            // **
        if(this.isRewardsEligible(user)) {
            this.price = this.price * 0.9
        }
    }
}

부분이 추가된 메서드 isRewardsEligible, getReward 입니다.
두 메서드는 사용자가 할인받을 자격이 있는지 판단하고, 할인가를 적용합니다.
getReward는 isRewardsEligible을 호출합니다.
인수로 받는 user객체에는 최소한 두개의 속성, rewardeligible과 active속성이 있어야합니다.

자식클래스 FlashCoupon은

  • 기간이 짧은대신 좀 더 큰 할인율을 제공하고 싶습니다.
  • 할인조건을 조금 더 까다롭게 하고 싶습니다.

자식클래스의 isRewardsEligible에서는

  • 부모클래스의 동일한이름의 메서드를 이용하고 price가 20이상일 때 라는 조건을 추가한다.

자식클래스의 getReward에서는

  • 클래스 내부의 isRewardEligible함수를 호출하고 20% 할인율을 적용한다.

    코드를 보겠습니다.

class FlashCoupon extends Coupon {
  constructor(price, expiration) {
    super(price)    // 부모클래스 생성자 호출
    this.expiration = expiration || '2시간'    // 속성 덮어씌움
  }
  getExpirationMessage () {    // 동일이름 메서드 대체
    return `이 쿠폰은 깜짝쿠폰이며 ${this.expiration}후에 만료됩니다.`
  }
  isRewardsEligible(user) {
    return super.isRewardsEligible(user) && this.price >20;    //**
  }
  getRewards(user) {
    if(this.isRewardsEligible(user)) {
      this.price = price * 0.8    //**
    }
  }
}

isRewardEligible메서드에서는 super.isRewardEligible을 호출합니다.
부모클래스의 메서드를 호출하려면 super로 접근해야 합니다. 그리고 비교연산자 &&를 붙여 20달러 이상일 경우를 추가했습니다.
getRewards 메서드에서는 this.isRewardsEligible로 현재클래스의 메서드를 호출하고 this.price로 현재 클래스의 price속성에 접근해 0.8을 곱해 20%할인율을 적용합니다.


2. 함수생성자와 prototype이용한 방법

포스팅의 시작부분에서 프로토타입과 클래스가 다르지않다고 밝혔습니다.
클래스타입과 프로토타입이 다르지않다는 점이 중요한 이유는 기존의 레거시프로젝트에서 프로토타입으로 상속을 구현했을 경우에도 class문법을 사용할 수 있다는 것입니다.
함수를 생성자로 사용하려면 코딩컨벤션(협약,규칙)에 따라 함수명을 대문자로 시작합니다.
constructor와 마찬가지로 함수 내부에서 this를 사용합니다.

동일한 클래스 Coupon을 함수 생성자를 이용해 만들어보겠습니다.

function Coupon (price, expiration) {
    this.price = price;
    this.expiration = expiration  || '2주'
}
const coupon = new Coupon(5,'2개월')
coupon price;

모든 객체 인스턴스는 프로토타입에서 속성을 가져옵니다.
getExpirationMessage()을 프로토타입에 추가해보겠습니다.

Coupon.prototype.getExpirationMessage = function () {
  return `이 쿠폰은 ${this.expiration} 후에 만료됩니다.`
}
coupon.getExpirationMessage();
// 이 쿠폰은 2개월 후에 만료됩니다.

확실히 클래스문법과 다른 점이 안느껴집니다.
실제로 class 키워드를 이용해서 객체를 생성할 때도 여전히 프로토타입을 생성하고 문맥을 바인딩합니다. ** 단지 class 키워드를 이용하면 더욱 직관적인 인터페이스를 사용할 수 잇다는 것 뿐입니다. **

그렇다면 클래스를 사용하는 가장 중요한 목적인 상속은 프로토타입으로 어떻게 구현하느냐가 궁금해집니다.

  1. 객체의 속성을 순회
  2. 개별 속성이 객체 프로토타입이 아닌 해당 객체에만 존재하는 속성인지 확인
  3. 메서드를 추가하기전에 부모로부터 새로운 객체에 프로토타입을 복사

이런 과정을 거치게 됩니다. (실제로 해보진 않았습니다. 안하는게 나을 것 같습니다.)
레거시코드가 프로토타입을 구현한 클래스라면

class문법을 사용해 기존 레거시코드를 확장하여 사용할 수 있다는 점과프로토타입과 class과 다르지 않다는 점을 짚고 넘어가면 되겠습니다.

감사합니다 !

다음 포스팅들에서는

  • class에서 get과 set사용하기
  • class에 generator 함수를 이용해 이터러블 구현하기 (중요)
  • class에서의 bind()와 화살표함수
    이 세가지에 대해 포스팅하겠습니다!
profile
웹 프론트엔드, RN앱 개발자입니다.

0개의 댓글