Inheritance(상속)의 개념에 대해서 살펴본다. 상속은 객체지향 프로그래밍을 위해 중요한 내용이다. 그러나 객체지향 프로그래밍 자체에 대한 이해나, execution context, this에 대한 이해가 없다면 정확한 이해가 어려울 수 있다.
우선 class와 instance 등 객체지향방식 프로그래밍에 대한 개념과 execution context, this에 대한 종합적 이해가 필요하므로, 아래 링크 글들을 정확히 이해해야 한다.
- Javascript / Execution context, this
- Javascript / 함수 / call, apply, bind
1. prototype과 상속의 개념
prototype은 '원형' 이라는 뜻으로, 어떤 함수의 속성 중 하나이다. 이 prototype 속성에는 어떤 객체가 담겨져 있어서 함수를 통해 새로운 instance를 생성할 때 객체를 전달해주게 된다. 콘솔 창에 Array.prototype을 쳐보면, 많은 함수들이 객체형태로 저장되어 있음을 볼 수 있다. 우리가 어떤 배열(array)을 생성한다는 것은, 이 Array.prototype을 상속받아오는 개념이 포함된 것이다. 따라서 prototype 안에 있는 함수를 활용하여 여러가지 기능 등을 만들어낼 수 있는 것이다.
Big 이라는 생성자는 prototype 이라는 속성에 apple이라는 method를 갖고, 그 값으로 'red'를 갖는다.
Small의 prototype은 Big의 instance로 지정하고, Tiny의 prototype은 Small의 instance로 지정하였다.
tinyOne 이라는 Tiny 생성자의 instance를 하나 만들어서 apple method를 적용해보면, 'red'라는 값이 출력됨을 확인할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 |
function Big() { Big.prototype.apple = 'red'; }
function Small() {} Small.prototype = new Big();
function Tiny() {} Tiny.prototype = new Small();
let tinyOne = new Tiny(); console.log(tinyOne.apple); // 결과 : red |
cs |
prototype이라는 속성을 통해서 생성자 내부의 method를 전달한 예시라고 할 수 있다.
(사실 이것은 아래 "4. 객체 생성 방법" 에서 볼 수 있듯이, 그다지 좋은 방식은 아니다.)
이렇게 prototype을 통해 객체를 전달하는 것을 prototype chain 이라고 한다.
2. 객체와 prototype의 생성 구조
let arr = new Array(); 로 새로운 Array를 생성한 뒤, arr.__proto__를 입력하면 constructor가 생성되어 있는 것을 확인할 수 있다. 여기서, constructor는 자신을 생성한 객체를 가리키며, prototype은 자식 객체를 생성할 때, 부모 역할을 하는 속성을 가리킨다. 내가 생성한 인스턴스에 .__proto__를 조회해보면, 그 부모요소가 constructor로 조회됨을 확인할 수 있다.
그리고 arr에서 Array.prototype에 붙은 모든 method를 활용할 수 있다. 즉, arr은 Array.prototype의 instance 중 하나이며 상위 요소의 요소들을 상속(inheritance) 받은 것이다.
위 Car를 만드는 예제를 바탕으로 살펴보면, instance를 하나 만들고, .__proto__를 통해 계속해서 상위요소로 올라가보면 결국
javascript에서 제공하는 가장 상위 객체인 Object를 가리킴을 확인할 수 있다.
즉, 모든 객체는 Object를 상속받게 된다.
좀 더 상세한 개념이해를 위해, 다음 그림을 보자.
(출처: medium.com/better-programming/prototypes-in-javascript-5bba2990e04b)
Human 이라는 함수(class)를 만들면, 그 안에 prototype이라는 property가 생성되게 된다.
그리고 이 property는 Prototype object를 가리키게 된다. prototype object 내부에 있는 constructor property는 다시 Human의 prototype property를 가리키게 된다.
Human class의 instance로 person1, person2를 만들면, 각각 '__proto__' 라는 property를 갖게 되며, 이것들은 모두 Human의 Prototype object를 가리키게 된다.
3. HTML 요소의 상속
HTML의 요소도 상속을 통해 이루어져 있다.
MDN 공식 문서(MDN: HTMLLIELEMENT - developer.mozilla.org/ko/docs/Web/API/HTMLLIElement )
li element를 하나 만들면, 그 위에 차례대로 HTML element, element, node, event target이 .__proto__ 로 존재한다는 뜻이다. event target 위에는 최종적으로 Object가 있다.
>> 각 method들이 어떤 요소들에서 정의되어 상속되었는지는, 작성 시 우측에 나오는 회색 글씨에 뜨게 된다.
4. 객체 생성 방법
4-1. Object.create(prototype) : Pseudoclassical
다음과 같이 Boss와 Employee class를 정의하고, Boss의 method인 role을 Employee가 상속받고자 한다고 가정해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// Boss class 생성
// role method 생성
var Boss = function(name){
this.name = name
}
Boss.prototype.role = function() {
console.log('sharing')
}
//-------------------------------//
// Employee class 생성
function Employee(name) {
Boss.call(this, name);
}
// Employee에 work method 생성
Employee.prototype.work = function() {
console.log('writing document')
}
// Employee의 instance 생성
let mark = new Employee('Mark')
mark.work() // 결과 : 'writing document'
|
cs |
Boss 라는 Class와 Employee라는 Class를 만들었다. Boss에는 "this.name = name"의 property와 "role"이라는 method를 생성해주었고, Employee에는 call을 통해 Boss의 property를 상속받았으며, "work"라는 method를 추가하였다.
아래에서 "mark"라는 Employee의 Instance를 생성하면, "mark"는 Employee의 Instance이므로 "work" method를 가지게 되어 "mark.work()"를 실행할 수 있게 된다.
Boss의 name property와 method를 상속받아 사용하고 싶다면, 아래와 같이 추가적인 처리가 필요하다.
0. Boss.call(this, name) 의 의미
Boss라는 함수를 실행하되, Boss 함수 안에 있는 this는 15번 줄에서 언급하는 this로 처리하라는 뜻이 된다. 그럼 15번 줄에서 언급하는 this는 무엇일까? Employee의 property를 정해줄 때 사용하는 this이다. 즉, Boss 함수를 정의할 때, "this.name = name" 이라고 정의하여 '향후 만들어질 Boss의 Instance의 이름을 모르니까 일단 this로 한다.' 라고 만든 것과 같은 맥락이다.
'향후 만들어질 Employee의 Instance의 이름을 모르니까 this로 하는데, this에 관한 property를 Boss에서 따와서 쓰겠다.' 라는 얘기가 된다. 이에 따라 Boss.call(this) 라는 것은 Employee의 this.name이 되는 것이고, Boss.call(this, name)이라는 것은 Employee에서 this.name = name을 쓰겠다는 의미가 된다.
1. Boss의 method 상속받기
위에서 언급한 바와 같이, Boss의 method는 Boss.prototype이라는 객체에 담겨지게 된다. 따라서 Boss의 prototype을 복사하여 Employee의 prototype에 할당해주도록 하는데, Object.create(부모생성자.prototype) 이라는 문법으로 사용하면 된다. 아래 그림에서와 같이, Employee.prototype = Boss.prototype으로 직접 할당하면 안된다. 이것은 Employee.prototype의 주소를 Boss.prototype의 주소로 참조만 하겠다는 얕은 복사를 의미하게 되기 때문이다.
Employee.prototype = Object.create(Boss.prototype);
2. prototype의 상위 요소 재설정
이제 boss의 property와 method를 모두 상속받았다. 아래 그림에서 볼 수 있듯이, 'mark' 라는 Instance를 생성하고 role method를 실행해보면 정상작동됨을 알 수 있다.
그러나, 아직 충분하지 않다...!
mark.role()은 잘 실행되지만, Employee.prototype의 constructor가 Employee가 아니라 Boss로 지정되어 있음을 확인할 수 있다. 따라서 아래 명령어를 통해 constructor를 변경해주어야 한다.
Employee.prototype.constructor = Employee
다소 복잡한 방식이였으나, 해당 방법은 원래 Javascript가 OOP 방식으로 개발된 것이 아니기 때문에 만들어진 하나의 대안적 방법이다. 그러나 원리와 구조를 이해하기에는 좋으므로 확실히 이해하고 넘어가도록 하자.
4-2. Class
class라는 키워드를 통해서 class를 만든다.
내부에 constructor를 작성하고, extends라는 키워드를 통해서 property 및 method를 상속받을 수 있다.
상위 요소를 가리킨다는 의미로 'super'를 사용하면 this keyword를 사용하지 않아도 된다. 입력하고 싶은 파라미터만 super()안에 넣어주면 된다. method의 상속을 위해서는, class 내부에서 'super.role()'과 같이 상위 Class의 method를 super 키워드를 통해 실행해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Boss {
constructor(name) {
this.name = name;
}
role() {
console.log('sharing');
}
}
class Employee extends Boss {
constructor(name) {
super(name);
}
work() {
console.log('writing');
}
super.role(); }
var emp1 = new Employee('Charlie');
emp1.role() // 결과 : sharing
|
cs |
만약, Employee(자식 요소)의 constructor가 Boss(부모 요소)와 같다면, constructor, super(bold체 부분)은 생략할 수 있다.
원리는 Object.create(prototype)과 같은 방식이나, javascript 문법이 발전되면서 편의가 제공된 것이다.
-추가 내용 : 명령어 a instanceof C
a라는 Instance가 C라는 class를 상속받았다면, true를 반환하고 아니면 false를 반환한다.
참조
생활코딩 : prototype
opentutorials.org/course/743/6573
Medium : Prototypes in Javascript
medium.com/better-programming/prototypes-in-javascript-5bba2990e04b
MDN: Inheritance in Javascript
developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Inheritance
'Programming-[Frontend] > Javascript' 카테고리의 다른 글
Javascript / 기초 / 비동기(Asynchronous), 콜백(callback), setTimeout (0) | 2020.09.21 |
---|---|
Javascript / Tips / MDN 문서에서 대괄호 [ ] (brackets)의 의미 (0) | 2020.09.18 |
Javascript / 배열, 객체 / 구조 분해 할당(destructing) (0) | 2020.09.05 |
Javascript / 객체 / Property shorthand (0) | 2020.09.05 |
Javscript / 함수 / 화살표 함수 (0) | 2020.09.01 |