Youtube | JavaScript Inheritance and the Prototype Chain - Tyler McGinnis 영상을 기반으로 작성한 글입니다.
의문의 시작은 '정의해 놓은 함수와 프로토타입 체인을 이용해 어떻게 다른 class를 만들 수 있을까?' 하는 것에서 부터 시작한다. 예를 들어 아래의 Animal라는 class와 같은 속성을 가지고 있지만 bark라는 속성이 추가 된 dog이라는 새로운 class를 생성해야 하는 것이다.
function Animal (name, energy) {
this.name = name
this.energy = energy
}
Animal.prototype.eat = function (amount) {
console.log (`${this.name} is eating.`)
this.energy += amount
}
Animal.prototype.sleep = function (length) {
console.log (`${this.name} is sleeping.`)
this.energy += length
}
Animal.prototype.play = function (length) {
console.log (`${this.name} is playing.`)
this.energy -= length
}
function Dog (name, energy,breed) { //Animal -> Dog로 수정
this.name = name
this.energy = energy
this.breed = breed
}
Dog.prototype.eat = function (amount) {
console.log (`${this.name} is eating.`)
this.energy += amount
Dog.prototype.sleep = function (length) {
console.log (`${this.name} is sleeping.`)
this.energy += length
Dog.prototype.play = function (length) {
console.log (`${this.name} is playing.`)
this.energy -= length
Dog.prototype.bark = function(){ //bark 속성을 추가
console.log (`Woof Woof`)
this.energy -= .1
}
const Charlie = new Dog('Charlie', 10, 'Goldendoodle')
Animal 함수를 그대로 가져와서 Dog이라고 이름만 바꿔준 다음에 원하는 속성을 추가해줬다. 근데 또 Cat이라는 class를 만들고 싶으면 어떡하지?? 또 전부 다 복사해서 cat의 특정 속성을 추가하는 방법을 사용해야 할까? Bird를 만들때도, Tiger를 만들 때도? 우리는 이 방법이 너무 번거롭다
는 사실을 파악할 수 있다.
👆우리는 Animal이라는 이미 생성되어 있는 기본 class를 기반으로하는 다른 동물들의 class를 쉽게 만들고 싶다.
그냥 Dog를 만들때 animal의 constructor 함수를 그대로 복사해오는 방법은 없을까?
function Dog(name,energy,breed){
Animal.call(this, name, energy) //Animal를 불러온다.
this.breed = breed //원하는 속성을 정의해준다.
const charlie = new Dog('Charlie', 10, 'Goldendoodle')
call을 이용하여 Dog함수 안에서 Animal 함수를 불러온 것을 확인할 수 있다.
그런데 charlie.eat을 실행해보면 eat이 이미 Animal class에 정의 된 속성임에도 불구하고 undefined가 출력된다. Animal 함수에 정의 된 속성들은 모두 불렀지만, Animal.prototype으로 연결해준 메소드들은 불러내지 못한 것이다. 그럼 Dog의 인스턴스가 Animal.prototype에 접근할 수 있게 하면 어떨까?
Object.create
를 이용하여 할 수 있다.
Object.create()란?
괄호 안에 들어가는 프로토타입 객체 및 속성을 가지는 새 객체를 만드는 메소드이다. 상위클래스의 메서드를 그대로 확장한 하위클래스의 객체를 생성할 때 사용한다. 반드시 하위클래스의 프로토타입의 constructor를 지정해주어야 한다. (Object.create()를 사용하면 프로토타입을 그대로 복사할 수 있지만 constructor 또한 함께 복사되어 상위클래스의 constructor로 바뀌게 된다.
function Dog(name,energy,breed){
Animal.call(this, name, energy)
this.breed = breed
Dog.prototype = Object.create(Animal.prototype)
const charlie = new Dog('Charlie', 10, 'Goldendoodle')
charlie.play(4) //'Charlie is playing.' --> Dog에는 정의 되지 않은 play라는 속성을 사용할 수 있다.
Dog에 bark라는 기능을 추가하고 싶다면 해당 코드를 추가해주기만 하면 된다.
Dog.prototype.bark = function(){
console.log (`Woof Woof`)
this.energy -= .1
}
이때 charlie라는 인스턴스는 Animal의 인스턴스가 되었지만 charlie의 constructor가 Dog가 아닌 Animal로 바뀌었다. Dog의 인스턴스인 charlie가 constructor는 Animal이라니? 어떻게 된 일이지?
Dog.prototype = Object.create(Animal.prototype)
위의 코드에서 활용한 Object.create는 Dog의 프로토타입에 새로운 constructor를 생성해주는 것이 아닌, Animal의 프로토타입을 그대로 위임해준다. 따라서 Animal constructor를 그대로 따라가게 되는 것이다.
Dog.prototype.constructor = Dog
다시 말해 constructor를 재정의해주는 과정이 필요하고 위의 코드를 추가하여 constructor를 지정해줘야 한다.
같은 동작을 ES6 class문법을 통해 더 간단하게 구현할 수 있다.
class Animal {
constructor(name, energy) {
this.name = name
this.energy = energy
}
eat(amount) {
console.log (`${this.name} is eating.`)
this.energy += amount
}
sleep() {
console.log (`${this.name} is sleeping.`)
this.energy += length
}
play() = function (length) {
console.log (`${this.name} is playing.`)
this.energy -= length
}
}
class Dog {
constructor(name,energy,breed) {
this.breed = breed
}
bark() {
console.log ('Woof Woof')
this.energy -= .1
}
}
Dog가 Animal의 속성을 그대로 포함하도록 하기 위해서 ES6에서는 정말 간단한 문법이 필요하다. extends라는 키워드를 사용하면 복사하고 싶은 class로 확장시킬 수 있다.
class SubClass extends BaseClass
이와 같은 원리로 코드를 수정해보면,
class Dog extends Animal { //해당 부분을 수정해주면 된다!
constructor(name,energy,breed) {
this.breed = breed
}
bark() {
console.log ('Woof Woof')
this.energy -= .1
}
}
그럼 Animal에만 있는 속성인 name,energy를 그대로 사용하고 싶은데 어떡하면 좋을까? ES5에서 call을 사용한 것 처럼 ES6에서는 super
키워드를 사용하면 된다.
super
는 우리가 extend한 constructor function을 부른다. 여기서는 Animal의 constructor function을 부른 것이다.
class Dog extends Animal {
constructor(name,energy,breed) {
super(name,energy) //super 키워드 추가
this.breed = breed
}
bark() {
console.log ('Woof Woof')
this.energy -= .1
}
}