자바스크립트는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어다. 자바스크립트는 객체 기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 "모든 것"이 객체다. 원시 타입(숫자열, 문자열, 블린, null, Boolean)의 값을 제외한 나머지 값들(함수, 배열, 정규 표현식)은 모두 객체다.
전통적인 명령형 프로그래밍의 절차지향적 관점(프로그래밍이란 명령어, 함수의 목록이다)에서 벗어나
여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 방식을 말한다.
다소 철학적인 접근인데, 이러한 여러 개의 독립적 단위를 의미화 하기 위한 속성을 자바스크립트는 부여한다. 우리가 아는 용어로 설명하면 바로 프로퍼티(property)이다.
const person = {
name: 'Lee',
address : 'Seoul'
}
person라는 객체(변수) 아래에 여러 개의 값을 하나로 구성하였다. 이러한 복합적 자료구조를 객체라고 하며, 이러한 독립적인 객체의 집합(다른 객체와 관계성; 메시지를 주고받거나[argument, parameter] 데이터를 처리[return])으로 프로그램을 표현하려는 방식을 객체지향 프로그램이라고 하는데, 가장 대표적인 프로그래밍 언어가 바로 자바스크립트인 셈이다.
객체지향 프로그램의 핵심 개념 "상속"
어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다. 이를 통해 얻는 이점은 불필요한 중복이 제거된다는 것이며, 이 말은 기존의 코드를 재사용함으로 개발 비용 및 노력을 큰 폭으로 축소시켰다는 것에 의미를 가진다.
function Students(entrance) {
this.name = entrance;
this.class = 1
this.alert = function() {
alert(`${this.name}님 환영합니다. ${this.class}반으로 배정되었습니다.`)
}
}
const adwin = new Students("Adwin");
const bdwin = new Students("Bdwin");
const cdwin = new Students("Cdwin");
17장에서 학습한 대로 "생성자 함수"를 통해서 손쉽게 새로운 객체를 생성하였다. 생성자 함수가 실행되면, 그 결과의 값으로 새로운 객체가 만들어졌을 것이다.
이렇게 만들어진 객체를 통해서 생성한 메서드를 위와 같이 호출할 수 있다. 그리고 해당 객체 안에 생성자 함수를 통해서 만들어진 내용이 저장된다.
문제는 여기서 발생되었다. 생성자 함수를 통해서 만들어진 객체의 메서드는 동일한 기능을 수행한다. 내용이 다를 것도 없다. 그런데 메모리를 차지한다. 이러한 고민에서 등장한 개념이 바로 프로토타입이다.
ES5이전, __proto__
는 비표준이었지만, ES6(2015년)에서 표준으로 채택되었으며, 현재 대부분의 브라우저에서 지원한다.
즉 중복되는 메서드를 별도의 원형(prototype)으로 분리하고 분리된 원형(prototype)을 생성자 함수에 상속시켜주는 것이다. 이러한 상속과 종속이라는 객체의 관계성을 통해서 자바스크립트는 불필요한 중복을 제거하고, 코드의 재사용성을 극대화함을 이뤄냈다. 예시를 살펴보자.
function Students(entrance) {
this.name = entrance;
this.class = 1
}
Students.prototype.alert = function() {
alert(`${this.name}님 환영합니다. ${this.class}반으로 배정되었습니다.`)}
const adwin = new Students("Adwin");
const bdwin = new Students("Bdwin");
const cdwin = new Students("Cdwin");
위의 차이가 보이는가? 생성자함수로 생성된 Adwin은 그 안에 메서드를 포함하고 있지만, 프로토타입의 상속과 함께 생성자함수로 생성된 Cdwin은 그 안에 메서드를 포함하고 있지 않다. 그렇다고 작동도 되지 않을까?
정상적으로 잘 작동한다.(cdwin.alert() 메서드 호출) 그러나 차이가 있다. 위의 codepen에서 볼 수 있듯이 생성자 함수로 생성된 객체 메서드는 서로 다른 메서드이지만, prototype의 상속과 함께 생성된 객체메서드(cdwin, ddwin)는 서로 동일한 메서드인 것을 확인할 수 있다. 그러나 상속된 객체의 this.name과 this.class은 각각 호출이 이뤄졌을 때 상속된 객체의 this의 값을 가져다가 사용하는 것이 다르다.
이와 같이 생성자 함수에서 별도로 원형을 만들고 상속을 시켜서 동작하게 하는 특징을 가진 것이 자바스크립트이며, 이것으로 얻는 이점은 메모리와 절약과 코드의 재사용성의 극대화이다.
객체지향 프로그래밍
다시 정리해보면, 객체의 상태를 나타내는 데이터(key)와 상태 데이터(value, 위의 사례 - 메서드)를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어서 생각하는 것을 말한다. 대표적인 사례가 위의 prototype 으로 생성자 함수 안에 있던 메서드를 독립화 시키고 이를 생성자함수에 귀속시킴으로 동일한 묶음의 불필요한 재생산이 아니라, 효율적인 재사용성을 높인 것이다.
프로토타입은 객체지향 프로그래밍(객체간의 상호작용)의 근간을 이루는 객체 간 상속을 구현하기 위해 사용한다.
위의 이미지에서 보는 생성자함수(원본객체)는 그로 인해 생성된 객체(복제객체)에도 상속된 프로퍼티(메서드포함)를 공유하며, 이렇개 복제된 객체는 자신을 생성한 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 공유하며 사용할 수 있다.
이런 의미에서 "생성자함수 - prototype - 객체"는 서로 연결되어 있다.
__proto__
위에서 생성된 객체 adwin에 대해서 살펴보자.
콘솔에서 OwnPropertyDesccriptors를 살펴보자.
function Students(name, age) { this.name = name; this.age = age; this.class = 1; } Students.prototype.alert = function() { alert(`${this.name}님 환영합니다. ${this.class}반으로 배정되었습니다.`); } const adwin = new Students("Adwin",19); console.log(Object.getOwnPropertyDescriptors(adwin))
분명 prototype 이 상속되어 있지만, 자신 내에서는 발견할 수 없는 것을 볼 수 있다. 그렇다면 아래와 같이 접근자 프로퍼티로 검색을 해보자.
콘솔에서 OwnPropertyDesccriptors(객체명, 'proto')를 살펴보자.
Object.getOwnPropertyDescriptors(adwin, '__proto__')
빈객체 obj를 생성하고, __proto__
를 통해서 parent 객체를 obj에 할당시켰다. 그리고 콘솔에 obj.x 를 호출하였다. 무슨일이 발생된 것일까?
이것이 접근자 프로퍼티(__proto__
)의 사례인데, 자바스크립트는 원칙적으로 다른 객체의 내부 슬롯과 메서드를 직접 접근 또는 호출할 수 없지만, 접근자 프로퍼티(__proto__
)를 통해서 간접적으로 이 과정을 허용시켰다.
Object.__proto__
을 통해 접근자 프로퍼티(__proto__
) getter/setter 함수를 실행한다. obj.__proto__
가 선언되면 getter함수가 실행되며, 객체 parent에 접근하여 값을 가져와 obj.__proto__
에 setter 함수를 통해서 할당시킨다. 그 결과 obj
는 빈객체였지만, 객체 parent의 값을 프로토타입을 통해서 취득한다.
이후 콘솔에 obj
의 프로퍼티를 물어보면 다음과 같은 답변이 돌아온다.
- 위의 객체 adwin의 사례에서 자신의 프로퍼티와 상속되어 공유된 프로토타입 프로퍼티의 사례에서 본 것 처럼, 객체 obj를 보자. 상속되어 공유된 프로토타입 밖에 없기 때문에 자신의 고유의 프로퍼티(!!)는 없다.
비록 자신의 고유의 프로퍼티로 프로토타입을 귀속시킬 수는 없지만, 프로토타입을 통해서 모든 객체는 공유되는 프로퍼티를 상속받아 사용할 수 있다. 그러나 그것은 그럼에도 공동의 프로퍼티이지, 자신만의 프로퍼티로 귀속되지 않는다.
그러나 본서의 저자에 의하면, 코드 내에서 __proto__
접근자 프로퍼티를 직접 사용하는 것은 지양된다. 해당 이슈에 대해서는 추후 19장 11절 "직접상속"의 부분에서 자세히 설명하기에, 여기에서는 넘어가자.
__proto__
먼저, prototype
- 사용주체 : 생성자 함수
- 사용목적 : 생성자 함수로 생성될 인스턴스에 프로토타입을 할당하기 위해
다음,
__proto__
- 사용주체 : 모든 객체
- 사용목적 : 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용
- 상속방법1) : Object.proto = otherObj
- 상속방법2) : Object.getPrototypeOf(Obj)
- 상속변경 : Object.setPrototypeOf(Obj, otherObj)
그러기에 생성자 함수의 prototype은 생성자 함수의 결과로 생겨난 새로운 객체의 __proto__
가 되며, 이 둘은 === true, 즉 동일하다.
Next. 프로토타입의 constructor 프로퍼티와 생성자 함수
Editor. EDWIN
date. 23/02/18