IM 4일차 (Pesudoclassical)
자바스크립트에서 Pesudoclassical 한 방법으로 OOP를 구현하는 방법과 ES6의 Class 키워드로 구현하는 방법을 알아보자.
1. Pesudoclassical
1 - 1) 객체(인스턴스) 생성하기
prototype 기반 객체 지향 프로그래밍에서 '클래스'의 개념을 대신하기 위해 채택된 것이 바로 '함수'다.
function Animal(name, speed) {
this.name = name;
this.speed = speed;
}
Animal.prototype.run = function() {
console.log(`${this.name}이 ${this.speed}의 속력으로 달린다`)
}
const myDog = new Animal('doggy', 30)
프로퍼티는 Animal에서 정의하고 메서드는 프로토타입 객체에 정의해주면 된다.
메소드를 꼭 프로토타입 객체에 정의를 해줘야만 모든 인스턴스가 해당 프로토타입을 참조해 run 메서드를 실행할 수 있다.
1 - 2) Inheritance(상속)
* call, apply 함수 메서드로 부모의 프로퍼티 상속받기
위 Animal 함수의 자식으로 Dog라는 함수를 만들고 프로퍼티와 메서드를 상속받아보자!
function Dog(name, speed) {
Animal.apply(this, [name, speed]) // or Animal.call(this, name, speed)
}
new 키워드를 통해 Dog 타입의 객체를 생성할 때 this가 Dog의 인스턴스를 가리키게 되어 해당 객체에 name 및 speed 속성이 생기게 된다.
* Object.create 메서드로 부모의 메서드 상속받기
call이나 apply를 사용하면 부모 함수의 속성을 상속받을 수 있으나, 부모 함수의 메서드는 상속받을 수는 없다.
왜냐하면 prototype 기반 객체 지향 프로그래밍에서는 메서드가 함수의 Prototype 객체에 정의되기 때문이다.
따라서 자식 함수의 Prototype 객체가 부모 함수의 Prototype 객체의 메서드를 상속받도록 코드를 작성해야 한다.
만약 아래와 같이 자식 함수의 프로토타입 객체에 부모 함수의 프로토타입 객체를 정의한다면 어떻게 될까?
Dog.prototype = Animal.prototype
만약 이런식으로 작성한다면 자식 함수인 Dog의 프로토타입 객체에 메서드를 추가할 때 Animal의 프로토타입 객체에도
해당 메서드가 추가될 것이다. 왜냐하면 서로 같은 객체를 참조하고 있기 때문이다. 물론 부모의 변화도 자식에게 영향을 끼친다.
이러한 방법은 상속이라고 볼 수 없다.
따라서 우리는 Object.create() 메서드를 활용하여 부모 함수의 메서드를 자식 함수에게 상속시킬 것이다.
Object.create() 함수는 인자로 객체를 받으며, 인자로 받은 객체를 Prototype 객체로 하는 새로운 객체를 생성해 리턴해준다.
function Animal(name, speed) {
this.name = name;
this.speed = speed;
}
Animal.prototype.run = function() {
console.log(`${this.name}이 ${this.speed}의 속력으로 달린다`)
}
function Dog(name, speed) {
Animal.apply(this, [name, speed]) // or Animal.call(this, name, speed)
}
Dog 함수가 상속받고자 하는 Animal 부모 함수의 메서드는 Animal.prototype 객체에 존재한다.
따라서,Animal.prototype 객체를 Object.create() 함수의 인자로 주어, 그 객체를 Prototype 객체로 하는 새로운 객체 B를 생성하고
이를 Dog 함수의 Prototype 객체로 설정해주는 다음과 같은 코드를 작성하면,
Dog.prototype = Object.create(Animal.prototype);
Dog의 Prototype 객체의 __proto__ 속성이 Animal.prototype 객체를 가리키게 되면서 Animal.prototype 객체와 Dog.prototype
객체 사이에 계층 구조가 생기게 되며, 이를 통해 부모-자식 관계가 생성되게 된다.
이제 Dog 의 인스턴스가 run 메서드를 호출하면, 자바스크립트는 먼저 해당 인스턴스에서 run 메서드를 찾아보고, 없다면 Object.create로 생성된 Dog의 Prototype 에서 찾아보고, 또 없다면 Dog.prototype의 __proto__ 속성이 가리키는 Animal.prototype
의 객체에서 찾아보고 그래도 없다면 계속 상위 prototype 객체로 거슬러 올라갈 것이다.
다만 우리가 주의할 점이 한 가지 있다.
* Dog.prototype 객체의 constructor 를 Dog 함수로 변경하기
앞서 Object.create(Animal.prototype) 메서드의 결과를 Dog.prototype 객체에 할당해주었는데 여기서 한 가지 문제점이 발생한다.
이전 포스트에서 프로토타입 객체에 정의된 constructor 함수는 기존 함수 자체를 바라보고 있다고 했다.
따라서 Dog.prototype.constructor === Dog 여야 한다.
그러나 지금 같은 경우 Dog.prototype.constructor === Animal 이다.
우리가 Animal 객체를 Prototype 객체로 가지는 새로운 객체를 만들 때 Object.create() 함수 호출 시 Dog 함수에 대한
아무런 정보를 주지 않았기 때문에, 자바스크립트는 새로 만들어진 객체의 constructor에 무슨 값을 넣어줘야 하는지 모른다.
따라서 Object.create()는 인자로 들어온 Animal.prototype의 constructor의 값을 그대로 복사해 새 객체의 constructor 값으로 설정해주며, 이 때문에 새 객체의 constructor가 Animal.prototype 객체의 constructor가 가리키는 Animal 함수를 가리키게 된 것이다.
해결 방법은 단순하다.
Dog.prototype.constructor = Dog;
다음과 같이 수동으로 바꿔주면 된다.