JavaScript에서는 객체를 상속하기 위해서 프로토타입이라는 방식을 사용한다.
JavaScript는 흔히 프로토타입 기반 언어(prototype-based language)라 불린다— 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는 의미
프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지이다. 이를 프로토타입 체인(prototype chain)이라 부른다.
정확히 말하자면 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의
prototype
이라는 속성에 정의되어 있습니다.
생성자.prototype
을 통해 프로토타입 객체에 접근할 수 있으며, 이 객체에는 해당 생성자를 통해 생성된 객체들이 사용할 수 있는 메소드와 constuctor
가 정의 되어있다.생성자.prototype.constructor === 생성자
이다. 즉 prototype과 constructor는 서로를 참조한다.생성자.prototype
으로 접근했다면 인스턴스.__proto__
를 통해서 프로토타입 객체에 접근이 가능하다. 즉 __proto__
속성을 통해서 자신을 생성한 생성자, 즉 부모의 프로토타입 객체로 접근할 수 있게 된다. 이를 prototype link라고 부른다. 참고로 __proto__
속성은 모든 객체가 갖는 속성이다. 따라서 프로토타입 객체도 가지고 있으며, 생성자 객체와 인스턴스 객체 모두가 가지고 있다.let A = String("안녕하세요") // A = "안녕하세요" 와 동일하다
//A는 인스턴스이고, String은 생성자이다.
//인스턴스 객체는 __proto__속성을, 생성자 객체는 prototype 속성을 가진다.
//두 가지 속성 모두 같은 프로토타입 객체를 가리킨다.
A.__proto__ === String.prototype //true
//프로토타입 객체에서는 String 생성자를 통해 생성된(상속받은) 객체들이
//사용할 수 있는 메소드들이 정의 되어있다.
A.__proto__.constructor === String //true
String.prototype.constructor === String
//프로토타입 객체에서 생성자로 접근할 수 있수도 있고, 생성자에서 프로토타입 객체로 접근할 수도 있다.
//즉 프로토타입 객체와 생성자(constructor)는 서로를 참조한다.
A.__proto__.__proto__ //Object의 프로토타입 객체
//프로토타입 객체에도 상속받은 메소드들이 정의되어 있는 __proto__속성이 있으며
//이를 통해 상위로 올라갈 수 있다.
//가장 상위에 있는 프로토타입 객체는 Object 생성자의 프로토타입 객체임을 알 수 있다.
//이 밖에 Array,Function,Number와 같은 생성자도
//프로토타입을 타고 상위로 올라가게 되면 Object 객체를 상속받는 것을 알 수 있다.
//자바스크립트의 거의 모든 요소들은 객체이고, 객체를 상속받기 때문에 객체 메소드를 사용할 수 있다.
//String 인스턴스의 경우 String,Object를 상속받기 때문에
//각각의 프로토타입에 정의되어 있는 전부 메소드들을 사용할 수 있다.
//다음의 경우도 마찬가지다.
function Person(name){
this.name = name;
}
let soohyun = new Person("soohyun")
//이 경우 soohyun은 인스턴스, Person은 생성자(constructor)이다.
Person.prototype.sleep = function(){console.log("졸려요")}
//생성자 Person의 프로토타입에 sleep라는 이름의 메소드(함수)를 정의해주었다.
//앞으로 Person 생성자를 통해 생성되는 모든 인스턴스들은 sleep 메소드를 사용할 수 있다.
//Person.prototype이나 soohyun.__proto__를 통해 프로토타입 객체에 접근할 수 있다.
Person.prototype.constructor === Person //true
기본적으로 프로토타입(Prototype)은 할당이 가능하다.
여기서는 Object.create()를 이용한 고전적인 상속방법에 대해 알아본다.
function Human(name){
this.name = name;
}
Human.prototype.sleep = function(){console.log("자는 중입니다")}
function Student(name){
}
//Human과 Student 생성자 함수를 각각 만들었다.
//Student가 Human의 프로토타입을 상속받게 하기위해서는 어떻게 해야할까
//Student.prototype = Human.prototype
//단순히 Human의 프로토타입객체를 Student의 프로토타입 객체에 할당하는 것만으로는 충분치 않다.
//이 경우 Human의 프로토타입 객체와 Student의 프로토타입의 객체가 완전히 같아지게된다.
//따라서 Student의 프로토타입 객체에 메소드를 추가하면, Human의 프로토타입에도 추가된다.
//즉 Student 프로토타입과 Human 프로토타입의 참조가 같아진다.
//우리가 원하는 것은 Student가 Human을 상속받되,
//Student 고유의 메소드도 사용하는 것이다.
Student.prototype = Object.create(Human.prototype)
//Object.create()를 통해 Student의 protoype에 Human prototype을 할당할 수 있다.
//이 경우 Human 프로토타입의 내용을 복사해 Student 프로토타입에 복사하는 것과 같다.
//즉 Human과 Student 프로토타입의 참조가 각각 다르게 되며,
//Student에서 고유의 메소드를 사용할 수 있다.
Student.prototype.learn = function(){console.log("배우는 중입니다")}
let soohyun = new Student("soohyun")
soohyun.sleep() //"자는 중입니다."
soohyun.learn() //"공부하는 중입니다."
soohyun.__proto__.constructor // Human 생성자
//하지만 이 경우에도 constructor가 여전히 Human 함수를 바라보고 있다.
Student.prototype.constructor = Student;
//construtor를 Student 함수로 바꾸어주었다.
soohyun.name //undefined
//하지만 또 this 바인딩이 제대로 되지 않아 파라미터의 name이 잘 전달되지 않았다.
function Student(name){
Human.call(this, name) //Human.apply(this, arguments)
}
//this 바인딩을 통해 파라미터가 제대로 전달되게끔 해주면 끝이 난다.
//아래는 모범답안 :)
function Human(name){
this.name = name;
}
Human.prototype.sleep = function(){console.log("자는 중입니다")}
function Student(name){
Human.call(this, name) //Human.apply(this, arguments)
}
Student.prototype = Object.create(Human.prototype)
Student.prototype.constructor = Student;
Student.prototype.learn = function(){console.log("배우는 중입니다")}
ES6
Class와 Super
Object.create()
를 이용해 상속을 하고자 할 경우 지정해줘야 할 것들이 너무 많아 번거롭다고 느꼈을 것이다.여기서는 상속을 간단하게 해줄 수 있는
ES6
문법인class
와super
에 대해 알아본다.
class Human {
constructor(name){
this.name = name;
}
sleep(){console.log("자는 중입니다.")}
}
class Student extends Human {
constructor(name) { //constructor구문은 부모 클래스와 동일하다면 생략가능
super(name);
}
learn(){console.log("공부하는 중입니다.")}
}
//function 대신 class키워드를 사용해 class 이름을 지정하고, constructor안에 속성을 정의한다.
//constructor 바깥에는 메소드를 정의한다.
//Human class를 상속한다는 의미에서 extends를 사용했으며
//call,apply대신에 super 키워드를 이용해 바인딩을 해줄 수 있다.
//이 코드의 경우 2번에서 구현한 것과 동일한 기능을 훨씬 간단하게 구현할 수 있다.