OOP란 개념을 배웠는데 자바스크립트에서는 OOP를 어떻게 구현할 수 있는지 알아보자.
자바스크립트는 클래스란 개념이 없어 프로토타입이란 매커니즘을 통해 OOP를 구현할 수 있다. 이 프로토타입 중에서도 상속을 통한 프로토타입 체인이란 개념이 중요하다.
프로토타입을 한마디로 정의하면 "원형 객체"이다.
prototype을 이용해 새로운 객체를 만들어 낼 수 있다.
prototype을 통해 만들어진 객체는 prototype 객체의 속성들을 물려받게 된다.
function Person(name, age) {
this.name = name;
this.age = age
}
let me = new Person('meme', 10);
prototype 객체는 기본적인 속성으로 constructor
와 __proto__
를 가지고 있다.
이 중 __proto__
에는 자식의 부모 prototype 객체를 가리키는 링크를 담고 있다.
프로토타입 체인은 __proto__
의 특징을 이용하여, 부모 객체의 프로퍼티나 메서드를 차례로 검색하는 것을 의미한다.
이 말의 의미는 특정 객체의 프로퍼티나 메서드 접근시 자신의 것 뿐 아니라 부모 객체의 것도 접근해서 사용가능하다는 것이다.
모든 프로토타입 체이닝의 종점은 Object.prototype이다.
Object는 가장 상위 객체이다. (모든 객체의 부모격)
아래는 Human이란 클래스에 생성자 함수와 sleep(), eat()메소드를 정의해줬다.
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
console.log(`${this.name}이 자고 있어요.`);
}
eat(food) {
console.log(`${this.name}이 ${food}를 먹고 있어요.`);
}
new 키워드로 생성자 함수를 불러서 jumi라는 인스턴스를 생성하게 되면
jumi는 name과 age 프로퍼티를 갖는 객체인데 sleep()이나 eat()메서드를 쓸 수 있다.
let steve = new Human('steve', 20);
steve; // { name: 'steve', age: 20}
steve.sleep(); //steve이 자고 있어요.
steve.eat(apple); //steve이 apple를 먹고 있어요.
이유는 __proto__
에 부모 객체인 Human의 링크를 담고 있어 부모 메서드를 불러다 사용하기 때문이다.
Human 클래스와 프로토타입과의 관계를 살펴보면 보면
프로토타입은 할당이 가능하다.
유사 배열을 만들고 싶을 때 Array의 프로토 타입을 할당해주면 배열처럼 작동해서 배열의 메서드들을 쓸 수 있다.
자신과 부모 프로토타입을 체인닝 할 수 있는 2가지 방법이 있다.
만약 위의 예시 처럼 Human 클래스를 만들고, Student라는 클래스를 하나 더 만들어 jumi라는 인스턴스를 생성한다고 할 때, jumi는 Student의 속성과 메서드만 사용할 수 있다.
이때, jumi는 학생이면서 사람이기도 하기때문에 Human의 속성과 메서드도 사용하고 싶다면 어떻게 가져다 쓸 수 있을까??
고전적인 방법으로 체인닝의 원리를 이해하는데에 의의를 두자 ㅎㅎ
ES6와서는 잘 쓰이지 않는 방법이다.
const Human = function(name, age) {
this.name = name;
this.age = age;
}
//메서드
Human.prototype.sleep = function() {
console.log(`${this.name}이 자고 있어요.`);
}
Human.prototype.eat = function() {
console.log(`${this.name}이 ${food}를 먹고 있어요.`);
}
const Student = function(name, major) {
this.name = name;
this.major = major;
}
Student.prototype.study = function() {
console.log(`${this.name}이 ${this.major}를 공부하고 있어요.`);
}
let jumi = new Student('jumi', 'English');
jumi; //{name: 'jumi', major: 'English'}
jumi.study(); // jumi이 English를 공부하고 있어요.
jumi.sleep(); // VM599:1 Uncaught TypeError: jumi.sleep is not a function
위 처럼 sleep()을 써주면 Student에 메서드로 정의되지 않았기 때문에 에러가 난다.
Student.prototype에 Human.prototype을 할당해주면 메서드를 사동할 수 있을지 봐보자.
sleep()을 쓸 수 있다. 하지만 이렇게 할당하게 되면 객체의 참조를 바꿔버리게 된거라 Human을 바라보게 되면서 Student의 메서드였던 learn()을 쓸 수 없다.
Student.prototype = Human.prototype;
jumi.sleep(); // jumi이 자고 있어요.
jumi.study(); // VM1195:1 Uncaught TypeError: jumi.study is not a function
Object.create()를 써서 Human.prototype을 카피한 것을 Student.prototype에 할당해주면 된다.
Student.prototype = Object.create(Human.prototype);
이렇게 카피한 것을 참조하도록 할당하게 해주면 두가지 추가 작업을 해줘야 제대로 된 체이닝이 된다.
const Student = function(name, major) {
//Human에 this
Human.call(this, name);
this.name = name;
this.major = major;
}
Student.prototype = Object.create(Human.prototype);
// constructor를 Student로 바꿔줌
Student.prototype.constructor = Student;
Student.prototype.study = function() {
console.log(`${this.name}이 ${this.major}를 공부하고 있어요.`);
}
ES6에서는 class란 새로운 개념이 등장하면서 부모를 상속받아 쓸 수 있게 되었다.
자식 클래스를 생성할 때 extends
로 부모 클래스를 상속받는다.
부모에 this로 전달할 때는 super()
를 써며, 이 super()
는 this
보다 위에 위치해야 한다.
class Human {
constructor(name) {
this.name = name;
}
sleep() {}
}
class Student extends Human {
constructor(name, age) {
super(name); //부모에 this로 전달
this.name = name;
this.age = age;
}
study() {}
}
안녕하세요~ 좋은 글 잘 읽고 갑니다 :)
완전 이해가 잘되네요~ 감사합니다아
"Student의 메서드였던 learn()을 쓸 수 없다." 이 문장에 오타가 있는 것 같은데 learn()이 아니라 study()인 것 같아요 :)