하루에 한 문제

프로토타입 체이닝 본문

Dev/JavaScript

프로토타입 체이닝

dkwjdi 2021. 4. 13. 00:50

앞서 여러번 말했지만, 자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있다. 이를 암묵적 프로토타입 링크라고하며 이런 링크는 모든 객체의 [[Prototype]]프로퍼티에 저장된다.

 

하지만 주의해야 할 점이 있다. 앞서 prototype프로퍼티에서 설명했던 함수 객체의 prototype 프로퍼티와 객체의 숨은 프로퍼티인 [[Prototype]]링크를 구분해야 한다!

 

우선 이 둘의 차이점을 알기 위해서는 자바스크립트의 객체 생성 규칙을 알아야 한다.

//Person 생성자 함수
function Person(name) {
    this.name = name;
}

var foo = new Person('foo');

console.dir(Person);
console.dir(foo);

코드를 그림으로 나타내보자면 이런 그림이 나온다.

  • Person()생성자 함수는 prototype프로퍼티로 자신과 링크된 프로토타입 객체를 가리킨다.
  • 생성된 foo객체는 Person()함수의 프로토타입 객체를 [[Prototype]]링크로 연결한다.
  • 결국, prototype 프로퍼티나 [[Prototype]]링크는 서로 같은 프로토타입 객체를 가리킨다.
  • prototype프로퍼티는 함수의 입장에서 자신과 링크된 프로토타입 객체를 가리키고 있지만 [[Prototype]]링크는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 내부의 숨겨진 링크로 가리키고 있다.
  • 즉 객체를 생성하는 건 생성자 함수의 역활이지만, 생성된 객체의 실제 부모역할을 하는 건 생성자 자신이 아닌 생성자의 prototype프로퍼티가 가리키는 프로토타입 객체다!

 

console.dir(Person)의 prototype프로퍼티 결과

console.dir(foo) [[Prototype]] 결과

 

  • Person() 생성자 함수의 prototype 프로퍼티와 foo 객체의 _proto_프로퍼티가 같은 프로토타입 객체를 가리키고 있다는 것을 알 수 있다.
  • 해당 프로토타입 객체는 constructor 프로퍼티가 Person() 생성자 함수를 가리키고 있다.

 

 

객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝

var myObject = {
    name: 'foo',
    sayName: function () {
        console.log("My name is" + this.name);
    }
}

myObject.sayName(); 
console.log(myObject.hasOwnProperty('name'));
console.log(myObject.hasOwnProperty('nickname'));
myObject.sayNickName();

  • myObject는 name 프로퍼티와 sayName()메서드를 가진 객체다.
  • 출력결과를 보면 sayNickName()메서드는 myObject의 메서드가 아니므로 오류가 난다
  • 그런데 hasOwnProperty()메서드는 myObject객체에 없지만 실행이 된 것을 볼 수 있다.
  • 우선 이를 이해하기 위해 객체 리터럴 방식으로 생성한 객체와 프로토타입 체이닝의 개념을 살펴보자
  • 객체 리터럴로 생성한 객체는 Object()라는 내장 생성자 함수로 생성된 것이다.
  • Object()생성자 함수도 함수 객체이므로 prototype이라는 프로퍼티 속성이 있다!
  • 따라서 생성한 객체 리터럴 형태의 myObject는 아래의 그림처럼 Object()함수의 prototype 프로퍼티가 가리키는 Object.prototype객체를 자신의 프로토타입 객체로 연결한다

 

 

 

앞의 개념을 이해했다면 이제 프로토타입 체이닝이라는 개념을 보자

자바스크립트에서 특정 객체의 프로퍼티나 메서드에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 [[Prototype]]링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것을 프로토타입 체이닝이라고 한다.

 

  • myObject 객체에서 sayName()이라는 메서드를 호출할 때는 객체 내 메서드가 존재해 바로 수행가능하다.
  • 반면에 hasOwnProperty()메서드는 객체 내에 존재하지 않는다. 이때는 [[Prototype]]링크를 따라가 부모 역할을 하는 Object.prototype 객체 내에 hasOwnProperty()메서드가 있는지 검색한다 
  • 이런 과정으로 에러가 나지 않고 정상적으로 코드를 수행할 수 있는 것이다.
  • sayNickName()메서드는 myObejct 객체와 Object.prototype객체에도 없으므로 에러가 발생한 것이다

 

생성자 함수로 생성된 객체의 프로토타입 체이닝

  • 생성자 함수로 객체를 생성하는 경우는 객체 리터럴 방식과 약간 다른 프로토타입 체이닝이 이뤄진다.
  • 하지만 두 가지 방식 모두 아래와 같은 기본 원칙을 잘 지키고 있다.
  • 자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모객체)로 취급한다
//Person() 생성자 함수
function Person(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
}

//foo객체 생성

var foo = new Person('foo', 30, 'tennis');

//프로토타입 체이팅
console.log(foo.hasOwnProperty('name')); //true

//Person.prototype 객체 출력
console.dir(Person.prototype);

  • foo객체의 생성자는 Person() 함수이다.
  • 따라서 foo객체의 프로토타입 객체는 자신을 생성한 Person 생성자 함수의 프로퍼티가 가리키는 객체(Person.prototype)가 된다.
  • 즉 이를 정리하면 foo객체의 프로토타입 객체는 Person.prototype가 된다.
  • foo.hasOwnProperty()메서드를 호출했지만, foo 객체는 hasOwnProperty()메서드가 없어 프로토타입 체이닝으로 foo의 부모 객체인 Person.prototype 객체에서 hasOwnPrototype()메서드를 찾는다. 하지만 이곳에도 메서드가 존재하지 않아 Object.protytpe객체로 가서 ahsOwnProperty()메서드를 찾아 실행한다.

 

 

프로토타입 체이팅 종점

  • 자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점이다. 
  • 앞서 봤듯이 객체 리터럴 방식이나 생성자 함수를 이용한 방식이나 결국에는 Object.prototype에서 프로토타입 체이닝이 끝나는 것을 알 수 있다.

 

기본 데이터 타입 확장

  • 앞서 자바스크립트의 모든 객체가 프로토타입 체이닝으로 Object.prototype에 정의한 메서드를 사용 가능한것을 확인했다.
  • 숫자, 문자열, 배열 등에서 사용되는 표준 메서드는 이들의 프로토타입인 Number.prototype, String.prototype, Array.prototype 등에 정의되어 있다.
  • 물론 이러한 기본 내장 프로토타입 객체 또한 Object.prototype을 자신의 프로토타입으로 가지고 있어 프로토타입 체이닝으로 모두 연결된다.
  • 자바스크립트는 Object.prototype, String.prototype 등과 같이 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드를 추가하는 것을 허용한다.
  • String.prototype 객체에 testMethod() 메서드를 추가해서 사용해보자.
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <script src="function.js"></script>
  </head>
  <body></body>
</html>

 

 

프로토타입도 자바스크립트 객체다

  • 함수가 생성될 때, 자신의 prototype 프로퍼티에 연결되는 프로토타입 객체는 디폴트로 constructor 프로퍼티만을 가진 객체다.
  • 당연히 프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제 하는것이 가능하다.
  • 그리고 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.
//Person() 생성자 함수
function Person(name) {
    this.name = name;
}

//foo 객체 생성
var foo = new Person('foo');

//foo.sayHello();

//프로토 타입 객체에 sayHello()메서드 정의
Person.prototype.sayHello = function () {
    console.log("Hello");
}

foo.sayHello() // Hello
  • 가장 처음 만나는 foo.sayHello()메서드는 정의되어 있지 않아 에러가 발생한다.
  • foo객체의 프로토타입 객체인 Person.prototype 객체에 동적으로 sayHello()메서드를 추가했다.
  • foo객체에서 sayHello()메서드를 호출했다. 이때 foo객체에는 sayHello()메서드가 없다. 하지만 프로토타입체이닝을 통해 Person.prototype 객체에서 sayHello()를 검색하고 실행한다.

 

프로토타입 메서드와 this바인딩

  • 프로토타입 객체는 메서드를 가질 수 있다.
  • 만약 프로토타입 메서드 내부에서 this를 사용하면 어디로 바인딩 될까??????
  • 답은 그 메서드를 호출한 객체에 바인딩된다!
//Person() 생성자 함수
function Person(name) {
    this.name = name;
}

//getName() 프로토타입 메서드
Person.prototype.getName = function () {
    return this.name;
}

//foo 객체 생성
var foo = new Person('foo');
console.log(foo.getName()); //foo

//Person.prototype 객체에 name 프로퍼티 동적 추가
console.dir(Person.prototype);
Person.prototype.name = 'person';
console.log(Person.prototype.getName()); //person

 

 

 

 

참고

book.naver.com/bookdb/book_detail.nhn?bid=7400243

'Dev > JavaScript' 카테고리의 다른 글

스코프 체인  (2) 2021.04.14
실행 컨텍스트  (0) 2021.04.13
함수 리턴  (0) 2021.04.09
함수 호출과 this  (0) 2021.04.09
함수 객체의 기본 프로퍼티  (0) 2021.04.07
Comments