오늘은 프로토타입에 대해 정리한다.
1. 프로토타입
# [[Prototype]]
- 자바스크립트 객체는 [[Prototype]]이라는 내부 프로퍼티를 가지고 있으며, 이 녀석은 다른 객체를 참조하는 단순한 레퍼런스로 사용된다.
- [[Prototype]] 링크는 현재 객체(
obj
)에서 찾고자 하는 프로퍼티(a
)를 찾지 못할 경우,obj
의 프로토타입 링크를 통해 프로토타입 체인을 거슬러 올라가면서 프로퍼티a
를 수색할 수 있도록 한다. - 이 연쇄를 따라 올라가면서 프로퍼티
a
를 찾고, 최상단에서도 찾지 못할 경우undefined
를 리턴하게 된다. - 또한
for ... in
루프로 객체를 순회할 때에도 [[Prototype]] 링크를 통해 객체 연쇄를 전부 찾는다. - 모든 자바스크립트 객체는 최상단의
Object.prototype
의 자식이다. 따라서 [[Prototype]] 연쇄는 최상단의Object.prototype
까지 올라가게 된다. - 만약
obj.foo = 123
으로 객체obj
에foo
프로퍼티를 추가하려고 하는데, 만약obj
의 프로토타입 체인의 상위에foo
라는 프로퍼티가 존재한다면, 현재 객체obj
에foo
프로퍼티가 선언됨으로써 상위의foo
프로퍼티는 영원히 가려지게 된다.
# 클래스 함수
new
키워드를 이용해 호출된 생성자 함수로 생성된 인스턴스는 프로토타입 객체와 [[Prototype]] 링크로 연결이 된다.
function Foo() {
// ...
}
const a = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; //
new Foo()
로 새로운 객체(a
)가 생성되며, 이 객체는Foo.prototype
객체와 내부적으로 [[Prototype]] 연결이 맺어진다.- 객체
a
와Foo.prototype
은 단지 서로 연결될 뿐이다. 같은 객체도 아니고,Foo.prototype
객체가a
에 복사되는 것도 아니다. 개별적인 두 개의 객체이다. - 결국
new Foo()
는 새로운 객체를 다른 객체와 연결짓기 위한 우회 방법 중 하나이며, 더 직접적으로는Object.create()
를 사용할 수 있다. - [[Prototype]] 체계를 다른 말로 '프로토타입 상속'이라고 부르는데, 이는 엄밀히 잘못된 표현이다.
- 상속은 기본적으로 복사를 수반한다. 즉, 상위 객체의 프로퍼티를 하위 객체에 복사한다는 의미인데, 자바스크립트에서 [[Prototype]] 링크는 그렇게 작동하지 않는다.
- 단지 a와 b 객체 간 연결만 지어줄 뿐이다. 따라서 '상속' 대신 '위임'이라고 부르는 것이 정확하다.
# 생성자
- 생성자 : 위의 코드 사례를 예로 들자면,
Foo.prototype
객체에는 기본적으로 열거 불가능한 공용 프로퍼티.constructor
가 세팅된다. 이는 객체 생성과 관련된 함수(Foo)를 다시 참조하기 위한 레퍼런스이다. - 함수는 생성자가 아니지만,
new
를 붙여 사용할 때에만 '생성자 호출'을 한다.new
로 생성자 호출을 하면 함수에 원래 해야할 작업 외 객체 생성이라는 추가 작업을 지시하게 된다.
# 생성자와 프로토타입의 체계
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function () {
return this.name;
};
let a = new Foo('a');
let b = new Foo('b');
a.myName(); // 'a'
b.myName(); // 'b'
function Foo() {
// ...
}
const a = new Foo();
a.constructor === Foo; // true
a.constructor === Foo
가 맞다고 해서, a 객체 내부에constructor
프로퍼티가 존재한다고 생각하면 틀렸다.- 단지 [[Prototype]] 위임을 통해서 전달받았을 뿐이다.
Foo.prototype
의.constructor
프로퍼티는 기본으로 선언된 Foo 함수에 의해 생성된 객체에만 존재한다.
# 프로토타입 상속
function Foo(name) {
this.name = name;
}
function Bar(name, label) {
Foo.call(this, name);
this.label = label;
}
Bar.prototype = Object.create(Foo.prototype);
Object.create()
는 '새로운' 객체를 생성한 뒤, 내부의 [[Prototype]]을 인자로 전달한 객체에 링크하는 메소드이다.Bar.prototype = Foo.prototype
처럼 할당하면 안된다. 이는 단지Bar.prototype
이Foo.prototype
객체를 가리키는 레퍼런스로 만들어, 사실상 Foo에 링크된 Foo.prototype 객체에 직접 연결한다.- 따라서
Bar.prototype.myLabel = ...
와같은 할당문은Foo.prototype
자체를 변경하게 되어 이와 연결된 모든 객체에 악영향을 미친다. - ES6 이후에는
Object.setPrototypeOf()
를 이용해서 기존 객체의 연결 정보를 수정할 수 있게 되었다.(Object.create()
는 기존 연결정보를 수정하는 것이 아니라, 리턴되는 새로운 객체로 아예 대체시켜버린다.)
// ES6 이전, Object.create()
Bar.prototype = Object.create(Foo.prototype);
// ES6 이후, Object.setPrototypeOf()
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
# 클래스 관계 조사
- 한 객체가 어떠한 객체를 위임받고 있는지를 알 수 있는 방법을 살펴보자.
a instanceof Foo
: 이는 a의 [[Prototype]] 연쇄를 순회하면서Foo.prototype
이 가리키는 객체가 있는지 조사한다.- 하지만
instanceof
는 대상 함수(Foo
)에 대해 주어진 객체(a
)의 계통만을 살펴볼 수 있다는 한계가 있다. 만약 2개의 객체(a, b)가 있을 경우, 두 객체가 서로 [[Prototype]] 연쇄로 연결되어 있는지는 알 수 없다. - 더 훌륭한 대안으로
isPrototypeOf()
를 사용할 수 있다. Foo.prototype.isPrototypeOf(a)
: 이는a
의 전체 [[Prototype]] 연쇄에Foo.prototype
이 존재하는지를 확인한다.- 또한 함수 대신
b.isPrototypeOf(c)
처럼 두 객체 간에 [[Prototype]] 연쇄가 존재하는지도 확인할 수 있다. Object.getPrototypeOf(a)
: a 객체의 [[Prototype]]을 조회하여, a에게 프로퍼티를 위임하고 있는Foo.prototype
를 조회할 수 있다.
# [[Prototype]] 링크는 대비책이 아니다.
- 만일 회사에서 개발할 때,
myObject.cool()
이란 메소드를 호출할 때,myObject
에cool
프로퍼티가 존재하지 않아도 정상적으로 동작하도록 설계되었다면, 추후 유지보수에 매우 어려움을 줄 수 있다.myObject
의 상위 링크에서cool
프로퍼티를 찾았기 때문에 동작했겠지만 이를 명시적으로 드러내지 않을 경우 오류를 초래할 수 있다. - 대신 아래처럼 명시적으로 상위 링크의 프로퍼티를 위임받는 '내부 위임' 격의 코드를 작성하는 것이 좋다.
let anotherObj = {
cool: function() {
console.log('cool');
}
};
let myObj = Object.create(anotherObj);
myObj.doCool = function () {
this.cool(); // 내부 위임
};
myObj.doCool();
'Javascript 공부 > TIL' 카테고리의 다른 글
200115(수) : import, export (0) | 2020.01.15 |
---|---|
200114(화) : var, let, const 차이 (0) | 2020.01.14 |
200109(목) : 부동소수점, git 경고메세지, 배열 중복 제거 등 (0) | 2020.01.09 |
200108(수) : 프로토타입, 객체지향 프로그래밍 (0) | 2020.01.09 |
200106(월) : 비동기, 고차함수, 일급객체, V8 엔진 등 (0) | 2020.01.07 |
댓글