-
prototype과 prototype Chain개발 2020. 4. 14. 23:46
prototype 이란 생성자 함수를 생성하면 prototype이라는 빈 객체가 어딘가에 존재하고, 생성자 함수( 부모객체 )로부터 생성된 객체( 자식객체 )들은 이 prototype이라는 객체에 들어있는 값을 모두 공유할 수 있습니다.
즉, 상속 개념과 마찬가지로 자식 객체는 부모 객체가 가진 속성이나 메서드를 상속받는 것이 가능합니다.이 전에 블로깅했던 생성자와 new 컨텐츠에서 다뤘던 생성자 함수 Person() 을 사용하여 더 자세히 설명해보도록 하겠습니다.
function Person(name, age, country){ this.name = name; this.age = age; this.country = country; this.introduce = function(){ return 'my name is ' + this.name; } } let person1 = new Person( 'hyojin', 25, 'seoul' ); console.log(person1.introduce()); // my name is hyojin
Person() 생성자 함수를 이용해서 person1 객체를 생성하였습니다.
person()생성자 함수는 prototype 프로퍼티로 자신과 링크된 프로토타입 객체( Person.prototye )를 가리킵니다.
그렇다면 person1 이라는 객체는 어떻게 생성자 함수인 Person에 접근하여 introduce() 메서드를 가져다 쓸 수 있었을까요?
prototype 속성은 함수만 가지고 있었던 것과는 달리 __proto__ 속성은 모든 객체가 빠짐없이 가지고 있는 속성입니다.
위 사진과 같이 person1을 보면 생성자 함수에서 초기화한 값들을 모두 공유하고 있고 마지막에 __proto__ 라는 프로토타입 링크가 들어있는 것을 볼 수 있습니다. 이 프로토타입 링크가 바로 생성자 함수 Person()의 내부 값들에 접근할 수 있는 열쇠인 것입니다.
결국, prototype 프로퍼티나 __proto__는 같은 프로토타입 객체를 가리키고 있습니다.
생성자 함수 Person()의 입장에서 prototype 속성은 생성자 함수인 자기 자신을 가리키고, Person을 통해 만들어진 person1객체의 입장에서 __proto__ 는 자신의 부모 객체인 프로토타입 객체를 가리키고 있기 때문입니다.
이 내용을 그림으로 설명하자면 다음과 같습니다.
이 그림을 콘솔 탭에서 보면 다음과 같습니다.
이 처럼 결과값을 살펴보면, Person() 생성자 함수의 prototype 프로퍼티와 person1 객체의 __proto__ 프로퍼티가 같은 프로토타입 객체를 가리키고 있다는 것을 알 수 있습니다.
따라서, 이러한 이유 덕분에 person1객체가 생성자 함수인 Person()의 내부 값들을 공유할 수 있다는 것을 확실히 알게 되었습니다.
그렇다면 이제 이전 컨텐츠인 생성자와 new 컨텐츠에서 Person 생성자 함수 내부에 있는 메서드를 어딘가에 단 한번만 정의해두고 새로 생성된 여러개의 객체들이 이 메서드를 공통적으로 사용하며 공유할 수 있다면 문제점이 해결되지 않을까 라는 의문점을 가졌었는데 이 부분을 충분히 해결할 수 있게 되었습니다.
person1의 부모객체인 생성자 함수 Person의 prototype을 자식객체인 person1이 공유하고 있으니 부모객체인 생성자 함수 Person의 prototype에 정의한다면 가능하지 않을까요? 다음 예제처럼 말입니다.
메서드를 prototype에 정의하기 전 코드 (생성자와 new 컨텐츠에서 작성했던 코드입니다.)
function Person(name, age, country){ this.name = name; this.age = age; this.country = country; this.introduce = function(){ return 'my name is ' + this.name; } } let person1 = new Person( 'hyojin', 25, 'seoul' ); console.log(person1.introduce()); // my name is hyojin let person2 = new Person( 'jack', 30, 'busan' ); console.log(person2.introduce()); // my name is jack
메서드를 prototype에 정의한 후 코드
function Person(name, age, country){ this.name = name; this.age = age; this.country = country; } Person.prototype.introduce = function(){ return 'my name is ' + this.name; } let person1 = new Person( 'hyojin', 25, 'seoul' ); console.log(person1.introduce()); // my name is hyojin let person2 = new Person( 'jack', 30, 'busan' ); console.log(person2.introduce()); // my name is jack
Person.prototype.introduce로 미리 정의해두고 person1, person2와 같이 객체가 생성될 때마다 introduce 메서드가 중복해서 매번 새로 만들어지지 않게 해주었습니다.
즉, 생성자 함수인 Person의 prototype에 한번만 정의해두고 자식객체를 생성할 때 자식객체의 __proto__ 프로퍼티로 연결고리를 만들어주어 이 메서드를 공유할 수 있게 된 것입니다. 당연히 그만큼 메모리낭비도 적겠죠?
person1객체에서는 name, age 등등 이러한 내부 값들을 직접 정의해주지 않았습니다. 부모 객체로부터 상속받은 것입니다.
그렇기 때문에 person1.name , person1.age 등등으로 접근할 때에 먼저 자기 자신( person1 )이 해당 값을 가지고 있는지를 확인한 후, 가지고 있지 않다면 부모 객체에서 찾습니다.
부모 객체에서도 찾지 못하면 부모객체보다 더 최상위 객체인 Object.prototype 객체까지 올라가서 찾고, 최상위 객체에서도 해당 값을 찾지 못한다면 undefined를 반환합니다. 이렇게 __proto__ 프로퍼티를 통해 상위 prototype과 연결되어있는 형태를 프로토타입 체인(Chain)이라고 합니다.
부모객체 즉, 생성자 함수가 제일 최상위객체인줄 알았는데 더 최상위 객체가 있다?
결론부터 말하자면, 부모객체보다 더 최상위인 객체가 있습니다. 바로 Object.prototype객체 입니다.
위의 예제를 계속해서 사용하여 설명하도록 하겠습니다. 먼저 객체의 hasOwnProperty() 이라는 메서드를 사용해서 확인해 보겠습니다.
Object.prototype.hasOwnProperty() 라는 메소드는 객체가 특정 프로퍼티에 대한 소유 여부를 반환합니다.
해당 객체에 특정 프로퍼티가 존재하면 true, 그렇지 않으면 false 를 반환합니다.
Object.prototype.hasOwnProperty() Mdn 링크! 이 예제에서 살펴보고자 하는 것은 name이라는 프로퍼티가 있는지가 아닌 person객체에 hasOwnProperty()라는 메서드가 있는지를 확인하고자 하는 것입니다.
function Person(name, age, country){ this.name = name; this.age = age; this.country = country; } let person1 = new Person( 'hyojin', 25, 'seoul' ); console.log( person1.hasOwnProperty('name') ); // true; console.log( Person.prototype );
person1.hasOwnProperty() 메서드를 호출했지만, person1객체는 hasOwnProperty() 라는 메서드를 정의한 적이 없기 때문에 프로토타입 체이닝으로 person1의 부모객체인 Person.prototype 객체에서 hasOwnProperty() 메서드를 찾습니다.
그러나 부모객체에도 이러한 메서드를 정의한 적이 없고, 함수에 연결될 프로토타입 객체는 디폴트로 constructor프로퍼티만을 가진 객체이므로 hasOwnProperty() 메서드는 없습니다. 정말 constructor 프로퍼티만 있는지 콘솔에서 확인해보겠습니다.
그렇다면 어떻게 person1.hasOwnProperty()가 true가 나왔을까요?
이유를 살펴보면, Person.prototype 역시 자바스크립트 객체이므로 Object.prototype을 프로토타입 객체로 가집니다.
따라서 프로토타입 체이닝은 Object.prototype 객체로 계속 이어진다는 말입니다. Object.prototype 객체에 정의 되어있는 hasOwnProperty() 메서드가 실행되므로 에러가 발생하지 않고 true가 출력되는 것입니다.
이 내용도 그림으로 표현하자면 다음과 같습니다.
이렇듯 자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점입니다. 결국엔 Object.prototype에서 프로토타입 체이닝이 끝나는 것을 알 수 있었습니다.
이것을 달리 해석하면, 위의 그림과 같이 모든 자바스크립트 객체는 프로토타입 체이닝으로 Object.prototype 객체가 가진 프로퍼티와 메서드에 접근할 수 있었기 때문에 여러가지의 많은 메서드들을 우리가 객체에 쓸 수 있었고, 서로 공유가 가능하다는 것을 알 수 있게 되었습니다.
'개발' 카테고리의 다른 글
객체의 메서드를 호출할 때 this 바인딩 (0) 2020.04.16 함수의 인수 (rest와 arguments 객체) (0) 2020.04.14 생성자와 new (0) 2020.04.12 객체지향 프로그래밍 (0) 2020.04.12 재귀함수 (0) 2020.04.12