[[Prototype]]이라는 다른 객체를 참조하는 프로퍼티를 생성한다.
```js
var anotherObject = {
a: 2
};var myObject = Object.create(anotherObject);
myObject.a; // 2
- `myObject.a`처럼 객체 프로퍼티 참조시 `[[Get]]`이 호출되는데, 기본적으로 객체 자체의 해당 프로퍼티가 존재하는지 찾아보고 존재하면 그 프로퍼티를 사용한다.
- `myObject`에 `a`란 프로퍼티가 없으면 이 객체의 `[[Prototype]]` 링크를 따라가서 수색한다.
- 이 경우, `[[anotherObject]]`에서 2라는 값을 대신 찾아서 프로퍼티 접근의 결괏값을 반환한다.
- 만약 `anotherOBject`에서도 못찾으면 `[[Prototype]]` 연쇄를 다시 따라 올라가고 연쇄 끝에 이르러서도 프로퍼티가 발견되지 않으면 `[[Get]]`은 결괏값으로 `undefined`를 반환한다.
- `for ... in` 루프에서 객체를 순회할 때도 `[[Prototype]]` 연쇄의 검색 과정과 비슷한 방식으로 연쇄를 통해 손길이 닿는 프로퍼티라면 죄다 열거한다.
### 1. `Object.prototype`
- `[[Prototype]]` 연쇄는 결국 내장 프로토타입 `Object.prototype`에서 끝난다.
- 모든 자바스크랩트 객체는 `Object.prototype` 객체의 자손이므로 여기에는 자바스크립트에서 두루 쓰이는 다수의 공통 유틸리티가 포함되어 있다. (`toString()`, `valueOf()`, `hasOwnProperty()`, `isPrototypeOf()`, ...)
### 2. 프로퍼티 세팅과 가려짐
```js
myObject.foo = "bar";
foo라는 이름의 평범한 데이터 접근 프로퍼티가 myObject 객체에 직속된 경우 이 할당문은 기존 프로퍼티 값을 고치는 단순한 기능을 할 뿐이다.foo라는 프로퍼티명이 myObject 객체와 이 객체를 기점으로 한 [[Prototype]] 연쇄의 상위 수준 두곳에서 동시에 발견될 때, 이를 가려짐이라 한다. (myObject의 직속 프로퍼티로 인한 상위 연쇄의 foo가 가려진다.)[[Prototype]] 연쇄를 순회하기 시작하고 그렇게 해도 foo가 발견되지 않으면 그제야 foo라는 프로퍼티를 myObject 객체에 추가한 후 주어진 값을 할당한다. 이럴 경우 사실 미묘한 일들이 벌어지는데 아래와 같다.
[[Prototype]] 연쇄의 상위 수준에서 foo라는 이름의 일반 데이터 접근 프로퍼티가 존재하는데, 읽기전용이 아닐 경우(writable: true), myObject의 직속 프로퍼티 foo가 새로 추가되어 결국 가려짐 프로퍼티가 된다.[[Prototype]] 연쇄의 상위 수준에서 foo라는 이름의 일반 데이터 접근 프로퍼티가 존재하는데, 읽기전용일 경우(writable: false), 어떠한 일도 일어나지 않는다. (엄격모드에서는 에러를 뱉는다.) 어쨋든 가려짐은 발생하지 않는다.[[Prototype]] 연쇄의 상위 단계에서 발견된 foo가 세터일 경우, 항상 이 세터가 호출된다. myObject에 가려짐 프로퍼티 foo를 추가하지 않으며 foo 세터를 재정의하는 일 또한 없다.2, 3번에서 foo를 가리려면 = 할당 연산자를 쓰면 안되고 Object.defineProperty() 메서드를 사용하여 myObject에 foo를 추가해야 한다.작동 위임(나중에 설명)이라는 대안적인 디자인 패턴을 통해 좀 더 깔끔하게 가려짐을 대체할 수 있다.var anotherObject = {
a: 2
};
var myObject = Object.create(anotherObject);
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("a"); // false
myObject.a++; // 허걱, 암시적인 가려짐이 발생한다!
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty("a"); // true
myObject.a++이 anotherObject.a 프로퍼티를 찾아 1만큼 증가시킬 것 같지만 ++ 연산자는 결국 myObject.a = myObject.a + 1을 의미해 의도와 다른 결과를 만들어낸다.anotherObject.a를 1만큼 증가시킬 의도라면 anotherObject.a++이 유일한 정답이다.자바스크립트는 여타 클래스 지향 언어에서 제공하는 클래스라는 추상화된 패턴이나 설계가 전혀 없고, 다만 객체만 있을 뿐이다.
function Foo() {
// ...
}
// 위와 같은 함수를 만들면 엔진은 함수의 내부에 Object.prototype을 프로토타입 링크하는 Foo.prototype이라는 객체 프로퍼티를 생성한다.
var a = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; // true
Foo의 프로토타입이라고 하는데, Foo.prototype이라고 명명된 프로퍼티 레퍼런스를 통해 접근하기에 그렇게 부른다.new Foo()로써 만들어진 모든 객체는 결국 Foo.prototype 객체와 [[Prototype]] 링크로 연결된다.[[Prototype]]으로 연결된 객체를 다수 생성하는 건 가능하지만 기본적으로 어떠한 복사도 일어나지 않아서 결과적으로 자바스크립트에서 객체들은 서로 완전히 떨어져 분리되는 것이 아니라 끈끈하게 연결된다.|
|상속은 기본적으로 복사를 수반 | 두 객체에 링크를 걸어두고 한쪽이 다른 쪽의 프로퍼티/함수에 접근할 수 있도록 위임한다.|function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
Foo 함수는 생성자가 아닌 그냥 보통 함수일 뿐이고, new 키워드를 통해 호출했을 때 생성자로서의 역할을 한다.Foo.prototype객체에는 기본적으로 열거 불가능한 공용 프로퍼티 .constructor가 세팅되는데, 이는 객체 생성과 관련된 함수(Foo)를 다시 참조하기 위한 레퍼런스다.new Foo()로 생성한 객체 a도 .constructor 프로퍼티를 갖고 있어서 자신을 생성한 함수를 가리킬 수 있다.a.constructor === Foo가 true임은 a에 Foo를 참조하는 .constructor라는 프로퍼티가 실재함을 의미할까???.constructor 역시 Foo.prototype에 위임된 레퍼런스로서 a.constructor는 Foo를 가리킨다..constructor 프로퍼티는 ‘~에 의해 생성됨‘이란 의미를 갖는게 아니다!
</p>
Foo.prototype의 .constructor 프로퍼티는 기본적으로 선언된 Foo 함수에 의해 생성된 객체에만 존재한다. 즉, 아래와 같을 경우에는 존재하지 않는다.
function Foo() { /* ... */ }
Foo.prototype = { /* ... */ }
var a1 = new Foo();
a1.constructor === Foo; // false
a1.constructor === Object; // true
a1에서 constructor를 찾았는데 없어서 Foo.prototype에서 찾고, 그 객체는 Object.prototype을 가리키고 있으므로 Object.prototype.constructor를 찾는다.
Object.defineProperty(Foo.prototype, "constructor", { enumerable: false, /* ... */ })와 같이 손수 삽입하는 일은 정말 하면 안되는 짓이다..constructor는 열거불가지만 값은 쓰기가 가능하며, [[Prototype]] 연쇄에 존재하는 constructor라는 이름의 프로퍼티를 추가하거나 다른 값으로 덮어쓰는 것도 가능하다..constructor와 같은 코드는 매우 불안정하고 신뢰할 수 없는 레퍼런스이므로, 될 수 있는 대로 코드에서 직접 사용하지 않는 게 좋다.function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name, label) {
Foo.call(this, name);
this.label = label;
}
// Bar.prototype을 Foo.prototype에 연결한다.
Bar.prototype = Object.create(Foo.prototype);
// 여기서 조심! 이제 Bar.prototype.constructor는 사라졌으니까
// 이 프로퍼티에 의존하는 코드가 있다면 수동으로 일일이 해결해야한다.
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar("a", "obj a");
a.myName(); // "a"
a.myLabel(); // "obj a"
Object.create()를 실행하면 난데없이 새로운 객체를 만들고 내부 [[Prototype]]을 인자로 받는 객체에 연결한다.Bar() { } 함수를 선언하면 Bar는 여타 함수처럼 기본으로 Bar.prototype 링크를 자신의 객체에 가지고 있다.Foo.prototype과 연결하고 싶은데, 현재 그렇게 연결되어 있지는 않다. 따라서 애초 연결된 객체와 헤어지고 Foo.prototype과 연결된 새로운 객체를 생성해 배당한 것이다.Object.create()를 사용해서 새로운 객체를 적절히 링크하는 방법 (주어진 기존 객체 자신을 수정하는 게 아니라 아예 내던지고 새로운 객체를 만들어 써야 한다는 단점이 있다.).__proto__ 프로퍼티를 거친 비표준적/비호환 방법Object.setPrototypeOf() 유틸리티
// ES6 이전
// 기존 기본 'Bar.prototype'를 던져 버린다.
Bar.prototype = Object.create(Foo.prototype);
// ES6 이후
// 기존 'Bar.prototype`을 수정한다.
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
b.isPrototypeOf(c);
c의 전체 [[Prototype]] 연쇄 어딘가 b가 존재하는가?