javascript는 모든 객체의 속성과 메소드를 상속 받기 위해서 prototype
을 사용합니다. 이렇게 상속 받은 객체는 또 그 상위의 객체에서 상속을 받을 수 있습니다. 이렇게 상속에 상속을 받으면서 연결 되는 것을 'Prototype Chain' 이라고 합니다.
__proto__
__proto__
는 생성된 인스턴스의 프로토타입을 조회하는데 사용된다.
const Human = function(name){
this.name = name;
} //Human을 생성하는 클래스
Human.prototype.study = function() { console.log("공부 중...")};
//Human 프로토타입에 study메소드 추가
const student = new Human('Ancoding');
//안코딩이라는 이름의 학생 생성
위 코드를 통해서 클래스와 인스턴스를 정의해 주었다. 여기서 생성된 인스턴스의 프로토타입과 휴먼의 프로토타입은 같을 것이다. 하지만 그걸 조회 하는 방법이 조금 다른데
인스턴스의 프로토타입 조회 :
인스턴스명
.__proto__
클래스의 프로토타입 조회 :클래스명
.prototype
위와 같은 형식으로 설명한다면
student.__proto__ === Human.prototype //true
가 성립하게 된다. 단, 여기서 주의 해야 할 점은 만약 클래스 안에 있는 prototype을 수정하고 싶으면 .prototype
만을 사용해서 수정해야 한다. __proto__
는 조회의 용도로만 사용해야 한다.
constructor
은 클래스 내에서 객체를 생성하고 인자를 받아 그 인자로 객체의 키와 키값을 초기화 하는 메소드이다.
class Car {
constructor(name, number){
this.name = name;
this.number = number;
}
}// Car클래스 정의 : constructor 메소드가 사용되어 있다.
let audi = new Car('A5',1234); //Audi라는 인스턴스 생성
위와 같이 코드를 입력 한다면 과연 Audi
에 들어가 있는 값은 무엇 일까?
앞서 설명한 constructor
에 의해서 Audi
는 객체로 생성된다.
typeof audi === 'object'; //true
완벽하게 이거다! 할 수는 없겠지만 내가 받아들이기에 메소드들의 저장소 라고 이해하고 있다. 앞서 말한 상속을 위해서 prototype
안에 여러가지 메소드를 정의해두고 이 메소드들을 자식 클래스들에게 상속해주기 위해 사용한다.
//prototype을 이용한 상속
//Human이라는 부모 클래스와 Student라는 자식 클래스가 있다고 가정한다.
Student.prototype = Object.create(Human.prototype);
//Student.prototype에 Human.prototype을 추가로 만들어준다.
//But, 이렇게 해버리면 Student의 constructor또한 Human의 것으로 바뀌어 버린다.
Student.prototype.constructor = Student;
//Student의 constructor를 다시 Student의 것으로 바꿔준다.
솔직히 이렇게 보면 '상속 = 자식 메소드에 부모 메소드 복사 붙여넣기'로 생각된다. 여기서 메소드들을 담고 있는 것이 prototype
이라고 생각된다. 상속을 할 때 마다 항상 이 코드들을 타이핑 하면 그 만큼 에너지 소비가 없기 때문에 향후 class
의 등장으로 상속이 조금 더 편해졌다.
javascript의 OOP를 더욱 수월하게 하기 위해 ES6부터 생겨난 고마운 친구다.
//ES5의 클래스 정의
function Car('name','number'){
this.name = name;
this.number = number;
Car.prototype.drive = function(){};
}
//ES6의 클래스 정의
class Car {
constructor('name','number'){
this.name = name;
this.number = number;
}
drive(){};
}
위 코드를 통해 본다면 ES5는 뭔가 클래스 보다는 함수에 가깝다는 느낌이다. 메서드를 정의 하는 방식 또한 훨씬 간결해지고 쉬워졌다는 걸 알 수 있다.
앞서 prototype
을 이용한 상속 방식을 보여줬다. 하지만 이 방식은 코드의 길이가 너무 길어지고 복잡해질 우려가 있다. 그런 불편함을 한번에 해소해줄 친구가 바로 super
메소드다. super
은 부모의 메서드를 호출할 때 사용된다.
class Parent {
constructor(name){
this.name = name;
}
parentFunction(){console.log('부모함수')};
} //먼저 부모 클래스를 하나 정의 해준다.
class Child extends Parent { //extends를 통해 상속 받을 부모를 정의해준다.
constructor(childName){
super(childName);//부모의 생성자 함수(constructor)를 가져와 쓴다.
//생성자 함수의 경우 그냥 super만 사용하면 된다.
}
useParentFunction() {
super.parentFunction();//부모 함수 가져오기
}
} //아이 클래스 생성
위 코드를 보면 super
의 사용법을 확실히 익힐 수 있다. 원리는 prototype
과 같지만 훨씬 간단하게 부모의 속성과 메소드를 상속 받을 수 있다.