As i wish

[You don't know JS] - 프로토타입 본문

JavaScript

[You don't know JS] - 프로토타입

어면태 2019. 5. 29. 00:49

이번에는 5장인 프로토타입에 대하여 공부해 보겠습니다!

 

1. [[Prototype]]

명세에 따르면 자바스크립트 객체는 [[Prototype]] 이라는 내부 프로퍼티가 있고 다른 객체를 참조하는 단순 레퍼런스로 사용합니다. 드물긴 하지만 [[Prototype]] 링크가 텅 빈 객체도 있긴합니다.

var myObject = {
    a: 2
}

myObject.a // 2

 위와 같이 객체의 프로퍼티가 있으면 [[Get]]을 이용하여 찾아서 출력하지만, 만약 a라는 프로퍼티가 없으면 다음은 이 객체의 [[Prototype]] 링크로 찾아서 확인 합니다.

var anotherObject = {
    a: 2
};

var myObject = Object.create(anotherObject)

myObject.a // 2


위 처럼 myObject 는 anotherObject 와 [[Prototype]] 이 링크 되었기 때문에 myObject 에 a 라는 프로퍼티가 없으면 anotherObject 에서 찾아서 반환 합니다. 만약 여기에도 없다면 동일한 프로퍼티 명이 나올 때 까지 [[Prototype]] 링크를 따라 올라갑니다. 그래도 없다면 undefined 를 반환 합니다.

1.1 Object.prototype

[[Prototype]] 연쇄가 끝나는 지점은 내장 프로토 타입 Object.prototype에서 끝납니다. 익숙한 .toString(), 이나 .valueOf() 도 다 Object.prototype 프로퍼티라고 생각하시면 될 듯 하네요.

2.1 프로퍼티 세팅과 가려짐

myObject.foo = 'bar'

위 코드 같이 프로퍼티가 myObject에 직속되는 경우라면 문제가 없지만 만약 앞서 설명한것 처럼 타고 올라가서 상위 [[Prototype]]의 어딘가에 존재하면 약간 다른 일이 벌어집니다. 만약 foo가 myObject객체와 이 객체를 기점으로 한 [[Prototype]] 연쇄의 상위 수준 두곳에서 동시에 발견되면 이를 '가려짐' 이라고 합니다.

var anotherObject = {
    a: 2
};

var myObejct = Object.create(anotherObject)

anotherObject.a; // 2
myObejct.a; // 2

anotherObject.hasOwnProperty('a') // true
myObejct.hasOwnProperty('a') // false

myObejct.a ++ // 가려짐 현상 발생

anotherObject.a // 2
myObejct.a // 3

myObejct.hasOwnProperty('a') // true

 위 코드 처럼 anotherObject 에 a 프로퍼티는 가려짐 현상이 발생하게 됩니다.

 

2. 클래스

앞서 설명 한 대초 자바스크립트는 여타 클래스 지향 언어에서 제공하는 클래스라는 추상화된 패턴이나 설계가 전혀 없습니다. 다만 객체만 있을 뿐이죠.

클래스와 객체의 혼합

 

[You don't know JS] - 클래스와 객체의 혼합

이번장에선 이론적인 설명이 많았습니다. 기존 객체지향 기반의 언어가 아닌 JS를 객체지향 언어처럼 쓰기 위한 노력들이 담겨 있었죠. 간단히 정리를 해보겠습니다. 1. 클래스 이론 클래스, 인스턴스에 대한 이론..

eomtttttt-develop.tistory.com

2.1 클래스 함수

function Foo() {
    /*
     ...
     */
}

Foo.prototype // {/* ... */}

위 처럼 모든 함수가 기본으로 프로토타입이라는 프로퍼티를 가지게 됩니다.

function Foo() {
    /*
     ...
     */
}

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

따라서 new Foo() 로 생성한 a는 Foo.prototype이 가리키는 객체를 내부 [[Prototype]] 과 연결되 있다는 것을 확인 해 볼 수 있죠. 앞선 포스팅에서 언급 했듯이 자바스크립트는 실체 객체를 복사하는 것이 아니라 어떤 공용 객체에 [[Prototype]] 으로 연결된 객체를 생성만 하는 것이죠. 즉 , 복사가 아닌 위임 또는 연결이죠.

2.2 생성자

function Foo() {
    // ...
}

Foo.prototype.constructor === Foo // true

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

Foo.prototype 객체에는 기본적으로 열거 불가한 공용 프로퍼티 .constructor 가 있는데요 이는 객체 생성과 관련된 함수 (Foo) 를 다시 참조하기 위한 레퍼런스입니다. 또한 마찬가지로 생성자 호출로 생성한 객체 a 도 .constructor 프로퍼티를 갖고 있어서 '자신을 생성한 함수'를 가리킬 수 있게 됩니다.

2.3 체계

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

Foo.prototype 객체의 프로퍼티/함수 가 a, bㅅ 생성 시 각각의 객체로 복사될 것 같지만 사실 그게 아니다. 앞서 언급한 것 처럼 객체에 직속된 레퍼런스가 발견되지 않으면 [[Prototype]] 링크를 찾아서 반환 하는데 a, b 생성 직후 각자의 내부 [[Prototype]]이 Foo.prototype에 링크가 되기 때문에 myName() 은 a, b가 아닌 Foo.prototype 에서 찾아서 반환 되는 것이죠.

function Foo() {
    // ...
}

Foo.prototype = {
    // ...
} // Set new prototype object

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

한가지 특이한 사실은 Foo.prototype의 .constructor 프로퍼티는 기본으로 선언된 Foo 함수에 의해 생성된 객체에서만 존재 합니다. 새롭게 .prototype 객체를 생성하게 되면 .constructor 는 따라다니지 않죠. 따라서 위 코드도 a1.constructor 가 [[Prototype]] 연쇄에 의해 따라 올라가다가 연쇄의 끝자락인 Object.prototype 만나게 되서 Object 를 가리키게 되는거죠.

그렇기 때문에 .prototype을 새롭게 정의하게 되면 이와 관련되있던 코드들은 문제가 생길 수 있으니, 다른 프로퍼티들도 다 재정의 해줘야 합니다.

3. 프로토타입 상속

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' 과 연결된 프로퍼티는 일일이 해결 해야한다.

Bar.prototype.myLabel = function() {
    return this.label;
}

var a = new Bar('a', 'obj a');

a.myName() // a
a.myLabel() // a

Bar() {} 함수를 선언하면 Bar는 여타 함수처럼 기본으로 .prototype 링크를 자신의 객체에 갖고 있습니다. 이 객체를 Foo.prototype 과 연결하고 싶은데, 현재 그렇게 연결되어 있지는 않습니다. 따라서 애초 연결된 객체와 헤어지고 Foo.prototype과 연결된 새로운 객체를 생성 (Bar.prototype = Object.create(Foo.prototype)) 하게 된거죠.

3.1 클래스 관계 조사

예를 들어 a 같은 객체가 어떤 객체로 위임할지는 어떻게 알 수 있을까요? 보통 전통적인 클래스 지향언어 에서는 인스턴스와 상속 계통을 살표보는 것을 인트로스펙션(리플렉션) 이라고 합니다.

function Foo() {
    // ...
}

Foo.prototype.blah = '...';

var a = new Foo();

a instanceof Foo; // true

그러나 위와 달리 객체 두개 예를 들어 a, b 가 있다고 가정 했을 때에는 두 객체가 [[Prototype]] 연쇄를 통해 연결 되어 있는지는 전혀 알 수가 없죠.

function Foo() {
    // ...
}

Foo.prototype.blah = '...';

var a = new Foo();

a instanceof Foo; // true

Foo.prototype.isPrototypeOf(a) // true

앞선 코드에 대안으로 위와 같이 해볼 수 있다. 위 코드는 Foo가 어떤 함수든 상관없이 다른 객체 테스트 시 사용할 객체 (Foo.prototype) 만 있으면 됩니다. isPrototypeOf() 는 'a 의 전체 [[Prototype]] 연쇄에 Foo.prototype이 있는가' 라는 질문에 대답해 줍니다.

b.isPrototypeOf(c);

c의 [[Prototype]] 연쇄 어딘가에 b가 존재하는가? 라는 물음 이죠.

4. 객체 링크

[[Prototype]] 체계는 다름 아닌 다른 객체를 참조하는 어떤 객체에 존재하는 내부 링크 입니다. 이 연결 고리는 객체의 프로퍼티/메서드를 참조하려고 하는데, 그런 프로퍼티/메서드 가 해당 객체에 존재하지 않을 때 주로 활용 됩니다. 자바스크립트 엔진은 [[Prototype]]에 연결된 객체를 하나씩 따라가면서 프로퍼티/메서드를 찾아보고 발견될 때 까지 같은 과정을 되풀이 하죠. 이렇게 객체사이에 형성된 일련의 링크를 '프로토타입 연쇄' 라고 합니다.

4.1 링크 생성

var foo = {
    something: function() {
        console.log('aaa');
    }
}

var bar = Object.create(foo);
bar.something() // aaa

Object.create() 는 먼저 새로운 객체를 생성(bar) 하고 주어진 객체 (foo) 와 연결합니다. 이것만으로도 앞서 계속 했었던 클래스나 생성자 호출, .prototype 이나 .constructor 레퍼런스등을 동원한 함수로 쓸데 없이 골치 아프지 않게 됩니다.

두 객체에 의미 있는 관계를 맺어주는 데 클래스가 필수 인건 아니죠. 객체의 위임 연결만 신경써서 잘 처리하면 되는데, Object.create() 덕분에 한줄로 깔끔하게 할 수 있다는 것을 보여주게 된거죠.

function Foo() {
    /*
     ...
     */
}

var a = new Foo();
var b = Object.create(Foo)

Object.getPrototypeOf(a) === Foo.prototype // true
Object.getPrototypeOf(b) === Foo // true

 

정리

- 객체에 존재하지 않은 프로퍼티를 접근하려고 시도 하면 [[Prototype]] 링크를 따라 수색 한뒤 프로퍼티를 찾아 반환 합니다.

- 모든 일반 객체의 최상위 프로토타입 연쇄에는 내장 Object.prototype 이 버티고 있습니다. 이 지점까지 이르러야 수색을 종료 합니다.

- 두 객체를 서로 연결 짓는 일반적인 방법은 new 키워드를 붙이는 것입니다.

- 새로운 객체와 손잡은 '다른 객체' 는 new를 이용하여 호출한 함수의 .prototype 이라고 임의로 명명한 프로퍼티를 통해 참조 됩니다.

- 자바스크립트는 전통적인 클래스 지향의 언어의 '클래스 인스턴스화 및 상속'과 유사해 보이지만 복사가 일어나지 않은 결정적 차이가 있습니다. 그러나 객체는 결국 다른 객체와 내부 [[Prototype]] 연쇄를 통해 연결되어 있죠.

- 사실 자바스크립트는 객체 간의 관계는 복사되는게 아니라 위임 연결이 조금 더 적절한 표현이라고 볼 수 있겠습니다.

 

Comments