자바스크립트 프로토타입 체이닝

Kim Jin Hyeok·2021년 2월 17일
0

프로토타입 체이닝

프로토타입의 두 가지 의미

자바스크립트는 기존의 C++이나 JAVA 같은 객체지향 언어와는 다른 프로토타입 기반의 객체지향 프로그래밍을 지원한다. 이번 절에서 자바스크립트에서 OOP 상속의 근간이 되는 프로토타입과 프로토타입 체이닝의 기본 개념을 살펴본다.

자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있다. 이 링크는 [[Prototype]] 프로퍼티에 저장된다.

여기서 주의할 점은 함수 객체의 prototype 프로퍼티와 [[Prototype]] 링크를 구분해야 한다. 이 둘의 차이점을 알려면 자바스크립트의 객체 생성 규칙을 알아야 한다.

자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 [[Prototype]] 링크로 연결한다.

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

var kim = new Person('kim');

console.dir(Person);
console.dir(kim);

결국, 자바스크립트에서 객체를 생성하는 것은 생성자 함수의 역할이지만, 생성된 객체의 부모 역할을 하는 것은 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체다.

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

자바스크립트 객체는 자기 자신의 프로퍼티 뿐만 아니라 부모 역할을 하는 프로토타입 객체의 프로퍼티 또한 자신의 것처럼 접근할 수 있는데, 이를 가능케 하는 것이 프로토 타입 체이닝이다.

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

myObject.sayName(); // My name is kim
console.log(myObject.hasOwnProperty('name')); // true
console.log(myObject.hasOwnProperty('nickName')); // false
myObject.sayNickName(); // Uncaught TypeError 발생

sayNickName() 메서드는 에러가 발생했지만 hasOwnProperty() 메서드는 정상실행이 되었다. 참고로 이 메서드는 인자로 넘긴 문자열 이름을 가진 프로퍼티나 메서드가 있는지 체크하는 자바스크립트 표준 API 함수다.

객체 리터럴로 객체를 생성하는 것은 사실 내부적으로 Object()라는 내장 생성자 함수를 이용한 것이다.

따라서 Object() 생성자 함수가 가진 prototype 프로퍼티가 가리키는 Object.prototype 객체를 myObject의 [[Prototype]] 링크가 가리킨다.

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

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

생성자 함수로 객체를 생성한 경우에도 객체 리터럴 방식과 약간의 차이는 있지만 기본 원칙은 같다.

자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모 객체)로 취급한다.

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

var kim = new Person('kim');

console.log(kim.hasOwnProperty('name')); // true

위 코드에서, 새로운 객체 kim의 프로토타입 객체는 Person.prototype 객체지만 hasOwnProperty()가 정상 실행된다. 그 이유는 Person.prototype 객체도 Object.prototype을 프로토타입 객체로 가지기 때문이다. 그림으로 나타내면 다음과 같다.

프로토타입 체이닝의 종점

자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점이다. 즉, 자바스크립트의 모든 객체는 Object.prototype 객체가 가진 프르퍼티와 메서드에 접근이 가능하다.

기본 데이터 타입 확장

Object.prototype 객체는 모든 객체의 표준 메서드라고 볼 수 있다. 모든 객체에서 호출 가능한 hasOwnProperty() 등의 표준 메서드를 가지고 있다는 말과 같다.

이와 같은 방식으로, 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, 이들의 프로토타입인 Numer.prototype, String.prototype, Array.prototype 등에 정의되어 있다.

또한 자바스크립트는 이러한 표준 프로토타입 객체에도 사용자가 직접 메서드를 추가하는 것을 허용한다.

String.prototype.testMethod = function () {
  console.log('call String.prototype.testMethod()');
};

var str = '';
str.testMethod(); // call String.prototype.testMethod()

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

프로토타입 객체 역시 자바스크립트 객체이므로 동적으로 프로퍼티를 추가하거나 삭제하는 것이 가능하다.

프로토타입 객체의 메서드 내부에서 this 바인딩은 규칙대로 그 메서드를 호출한 객체에 바인딩된다.

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

Person.prototype.getName = function() {
  return this.name;
}

var kim = new Person('kim');

console.log(kim.getName()); // kim 

console.log(Person.prototype.getName()); // undefined

Person.prototype.name = 'Person';

console.log(Person.prototype.getName()); // Person

디폴트 프로토타입을 다른 객체로 변경하기

디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되며, 함수의 prototype 프로퍼티에 연결된다.

자바스크립트에선 이러한 디폴트 프로토타입을 다른 일반 객체로 변경하는 것이 가능하다.

function Person(name) {
  this.name = name;
}
console.log(Person.prototype.constructor); // Person(name)

var kim = new Person('kim');
console.log(kim.age); // undefined

Person.prototype = {
  age: 29
};
console.log(Person.prototype.constructor); // Object()

var lee = new Person('lee');
console.log(kim.age); // undefined
console.log(lee.age); // 29
console.log(kim.constructor); // Person(name)
console.log(lee.constructor); // Object()

이러한 특징을 이용해 객체지향의 상속을 구현할 수 있다.

객체 프로퍼티 읽기나 메서드 호출시에만 프로토타입 체이닝 동작

당연한 소리다. 객체에 없는 프로퍼티 값을 쓰려고(write) 할 경우 동적으로 객체이 프로퍼티를 추가하기 때문이다.

참고: 송형주, 고현준, 인사이드 자바스크립트(2014)

0개의 댓글