자바스크립트에서 프로토타입이란

Jin·2022년 3월 1일
0

Javascript

목록 보기
13/22

자바스크립트 (이하 JS) 객체에는 [[Prototype]]이라는 내부 프로퍼티가 있고 다른 객체를 참조하는 단순 레퍼런스로 사용됩니다.

var otherObj = {
  a: 2
};

var myObj = Object.create(otherObj);
console.log(myObj.a); // 2

myObj는 otherObj와 [[Prototype]]이 링크됩니다. myObj.a란 프로퍼티는 없지만 otherObj에서 2라는 값을 대신 찾아 프로퍼티 접근의 결괏값으로 반환합니다. 만약 otherObj에서도 a라는 프로퍼티를 찾지 못했다면 프로토타입 연쇄를 다시 따라 올라가고 일치하는 프로퍼티명이 나오거나 프로토타입 연쇄가 끝날 때까지 이 과정은 반복됩니다. 연쇄 끝에 이르러서도 프로퍼티가 발견되지 않으면 결괏값으로 undefined를 반환합니다.

프로토타입 연쇄가 끝나는 지점은 결국 내장 프로토타입인 Object.prototype에서 끝납니다. 모든 JS 객체는 Object의 자손이기 때문입니다.

myObj.foo = "bar";

foo라는 프로퍼티가 myObj라는 객체에 직속된 경우에 이 할당문은 기존 값을 바꾸는 단순한 기능을 수행합니다.

프로토타입 연쇄를 순회하기 시작하고 그렇게 해도 foo가 발견되지 않으면 그때 foo라는 프로퍼티를 myObj 객체에 추가한 후 주어진 값을 할당합니다.

foo라는 프로퍼티명이 myObj 객체와 프로토타입 연쇄의 상위 수준 두 곳에서 동시에 발견될 경우에는 가려짐 현상이 발생합니다. myObj에 직속한 foo 때문에 상위 연쇄의 foo가 가려지는 것입니다. 이는 myObj.foo로 검색하면 언제나 연쇄의 최하위 수준에서 가장 먼저 foo 프로퍼티를 찾기 때문입니다.

foo 프로퍼티가 myObj에는 없고 상위 연쇄 어딘가에 있는 경우에는 다음 세 가지 경우의 수를 따릅니다.

  • 해당 프로퍼티가 읽기 전용이 아닐 경우 myObj의 직속 프로퍼티로 foo가 새로 추가되어 결국 가려짐 현상이 발생합니다.
  • 해당 프로퍼티가 읽기 전용이라면 엄격 모드에서는 에러가, 비엄격 모드에서는 조용히 무시됩니다.
  • 상위 연쇄 단계에서 발견된 프로퍼티가 세터일 경우 항상 이 세터가 호출됩니다. myObj에 프로퍼티가 추가되지 않으며 foo를 재정의하지도 않습니다.

가려짐은 그 이용 가치에 비해 지나치게 복잡하고 애매한 구석이 있으니 될 수 있으면 사용하지 않는 것이 좋습니다.

클래스

JS는 여타 클래스 지향 언어에서 제공하는 클래스라는 추상화된 패턴이나 설계가 전혀 없습니다.

JS는 클래스 없이 곧바로 객체를 생성할 수 있으므로 가장 '객체 지향 언어'에 가깝다고 할 수 있습니다.

JS에서는 모든 함수가 기본으로 프로토타입이라는 공용의 열거 불가 프로퍼티를 가집니다.

function Foo() {

}

Foo.prototype; // {}

Foo.prototype을 우리는 보통 Foo의 프로토타입이라고 하는데 Foo.prototype이라고 표기된 객체가 더 정확합니다.

function Foo() {

}

var a = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; // true

new Foo()가 하는 일은 a가 생성될 때 Foo.prototype이 가리키는 객체를 a의 내부 [[Prototype]]과 연결하는 것입니다.

클래스 지향 언어에서는 이렇게 new로 클래스 인스턴스화를 할 때 객체가 복사되지만 JS에서는 이런 복사 과정이 전혀 없고 클래스에서 여러 인스턴스를 생성할 수도 없습니다.

new Foo()로 새 객체 (a)가 만들어지고, 이 객체는 Foo.prototype 객체와 내부적으로 [[Prototype]]과 연결이 맺어집니다. 결국, 두 개의 객체가 상호 연결되는 것뿐입니다.

상속

상속은 기본으로 복사를 수반하지만, JS는 객체 프로퍼티를 복사하지 않습니다. 대신 두 객체에 링크를 걸어두고 한 쪽이 다른 쪽의 프로퍼티/함수에 접근할 수 있게 위임합니다.

생성자

function Foo(name) {
  this.name = name;
}

Foo.prototype.myName = function() {
  return this.name;
}

var a = new Foo('a');
var b = new Foo('b');

a.myName(); // a
b.myName(); // b

여기서 a와 b는 Foo라는 객체가 복사되는 것이 아닙니다.

새로운 객체가 생성되지만 내부 [[Prototype]]이 Foo.prototype에 링크된 것입니다.

JS에서 생성자는 객체가 복사됨을 의미하지 않습니다.

Foo는 생성자가 아닌 그냥 여느 함수일 뿐입니다.

constructor

function Foo(name) {
  this.name = name;
}

Foo.prototype = { ... };

var a = new Foo();
a.constructor === Foo; // false
a.constructor === Object; // true

위의 코드에서 알 수 있다시피, Foo.prototype의 .constructor 프로퍼티는 기본으로 선언된 Foo 함수에 의해 생성된 객체에만 존재합니다. a의 constructor는 .constructor 프로퍼티가 없으므로 프로토타입 연쇄를 따라 올라다가 연쇄의 끝인 Object.prototype 객체에 이르러서 .constructor 프로퍼티를 갖고 있으니 결국 내장 함수를 가리키게 되는 것입니다.

constructor 프로퍼티는 실제로 추가하거나 다른 값으로 덮어쓰는 것도 가능합니다.

a.constructor 같은 임의의 객체 프로퍼티는 실제로 기본 함수를 참조하는 레퍼런스라는 보장이 없으므로 코드에서 직접 사용하지 않는 것이 상책입니다.

결론적으로, JS에서 객체 간의 관계는 복사되는 것이 아니라 위임 연결이 맺어진 것이므로 '위임'이라고 해야 더 적절한 표현입니다.

profile
배워서 공유하기

0개의 댓글