안녕하세요.
이번 포스팅에서는 자바스크립트의 Class에 대해서 살펴보겠습니다.
기존의 자바스크립트에서는 prototype을 이용해 유사하게 상속을 구현했었습니다. ES6이후 Class가 나오면서 명료하고 간편하게 객체를 생성하고, 상속을 구현할 수 있습니다.
내부적으로 class는 기존의 prototype을 연결하는 속기법일 뿐입니다.
인스턴스가 생성되면 속성과 메서드가 복제되는 것이 아닌, prototype에 대한 연결을 생성합니다.
코드부터 살펴보겠습니다.
지금부터 만드는 코드는 Coupon이라는 클래스로, 만료기한과 가격이라는 속성이 있으며 해당 클래스를 상속하는 다른 쿠폰클래스를 만들기도 합니다.
class Coupon {
}
const coupon = new Coupon()
coupon에 Coupon클래스의 인스턴스를 생성했습니다.
함수처럼 표현식,선언식이 있지만 그런 부분은 제쳐두겠습니다.
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에 속성을 할당하며
인수를 이용해 인스턴스마다 다른 속성을 부여할 수 있습니다.
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로 현재 클래스의 속성에 접근할 수 있습니다.
할인율이 높고 기간이 짧은 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()메소드가 호출됩니다.
수정된 부모클래스 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에서는
자식클래스의 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%할인율을 적용합니다.
포스팅의 시작부분에서 프로토타입과 클래스가 다르지않다고 밝혔습니다.
클래스타입과 프로토타입이 다르지않다는 점이 중요한 이유는 기존의 레거시프로젝트에서 프로토타입으로 상속을 구현했을 경우에도 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 키워드를 이용하면 더욱 직관적인 인터페이스를 사용할 수 잇다는 것 뿐입니다. **
그렇다면 클래스를 사용하는 가장 중요한 목적인 상속은 프로토타입으로 어떻게 구현하느냐가 궁금해집니다.
이런 과정을 거치게 됩니다. (실제로 해보진 않았습니다. 안하는게 나을 것 같습니다.)
레거시코드가 프로토타입을 구현한 클래스라면
감사합니다 !
다음 포스팅들에서는