프로토타입 체이닝

프로토타입 체이닝

자바스크립트는 프로토타입 기반 객체지향 언어이다. 자바스크립트를 이해하기 위해서 무조건 알아가야 할 개념 중 하나가 이 프로토타입이다.
그래서 오늘은 상속의 개념을 구현하는 프로토타입과 프로토타입 체이닝에 대해 글을 써보려고 한다.

가장 중요한 건 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모로 설정하는 [[prototype]]링크로 연결한다. 이다.

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

var foo = new Person('Jang');

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

이 코드는 그림 한장으로 설명할 수 있다.

프로토타입

결국 prototype 프로퍼티나 [[prototype]]은 같은 객체를 가리키고 있다는 걸 알 수 있다.
즉, 객체를 생성하는 건 생성자 함수가 하지만 그 부모 역할을 하는 건 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체이다.

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

프로토타입 체이닝이란 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 사용할 수 있는 것을 말한다.

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

myObj.sayName();
console.log(myObj.hasOwnProperty('name'));
console.log(myObj.hasOwnProperty('nickName'));
myObj.sayNickname();

출력결과

My name is Jang
true
false
TypeError: myObj.sayNickname is not a function

hasOwnProperty() 메서드는 호출한 객체에 인자로 넘긴 문자열 으림의 프로퍼티나 메서드가 있는지 체크하는 API 함수이다.
myObj에는 name 프로퍼티가 있기 때문에 true가 출력되었다. 하지만 myObj에서 hasOwnProperty()를 사용할 수 있는 이유는 [[prototype]] 링크로 연결된 Object.prototype 객체에 hasOwnProperty() 메서드가 존재하기 때문이다.

결국 프로토타입 체이닝은 해당 객체에 접근하려는 프로퍼티나 메서드가 없다면 [[prototype]]링크를 따라서 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것을 말한다.

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

function Person(name, age, hobby) {
  this.name = name;
  this.age = age;
  this.hobby = hobby;
}

var foo = new Person('Jang', 27, 'Soccer');
console.log(foo.hasOwnProperty('name'));
console.dir(Person.prototype);

출력결과

true
Person {}(Object)

Person() 함수가 생성한 foo 객체의 프로토타입 객체는 Person.prototype이 된다. 하지만 Person.prototype에는 hasOwnProperty 메서드가 없다. 하지만 결과값은 true가 나오는데 그 이유는 프로토타입 체이닝의 종점인 Object.prototype 객체에 존재하기 떄문이다.
Person.prototype 역시 객체이기 때문에 Object.prototype을 프로토타입 객체로 가진다.

프로토타입체이닝

기본 데이터 타입 확장

숫자, 문자열, 배열 등이 사용하는 표준 메서드는 그들의 프로토타입에 정의되어 있다. 또한 기본 내장 프로토타입도 Object.prototype을 자신의 프로토타입으로 가지고 있다.(알면 알수록 놀랍습니다…) 또한 자바스크립트는 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드를 추가할 수 있다.

String.prototype.testMethod = function() {
  console.log('Testing method in String.prototype.testMethod()');
};

var str = "this is test";
str.testMethod();

console.dir(String.prototype);

출력 결과

Testing method in String.prototype.testMethod()
{ [String: ‘’] testMethod: [Function] }

구글 크롬의 브라우저 출력화면을 보면 다양한 메서드가 쭉 나오는데 testMethod가 추가된 것을 볼 수 있을 것이다.(꼭 해보시길!)

프로토타입 메서드와 this

앞에서 봤던 객체의 메서드를 호출할 때 this바인딩
의 규칙과 같다. 그 메서드를 호출한 객체에 바인딩 된다.

디폴트 프로토타입

디폴트 프로토타입은 한 마디로 함수가 생성될 때 같이 생성된다. 자바스크립트에서는 이러한 디폴트 프로토타입을 다른 일반 객체로 변환할 수 있다.
단, 변경된 시점에서 생성된 객체들은 변경된 프로토타입 객체로 [[prototype]] 링크를 연결한다.

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

console.log(Person.prototype.constructor); // 1

var foo = new Person('Jang');
console.log(foo.country); // 2

Person.prototype = {
  country : 'Republic of Korea'
};
console.log(Person.prototype.constructor); //3

var bar = new Person('Lee');
console.log(foo.country); //4
console.log(bar.country); //5
console.log(foo.constructor); //6
console.log(bar.constructor); //7

출력결과

[Function: Person] // 1
undefined // 2
[Function: Object] // 3
undefined // 4
Republic of Korea // 5
[Function: Person] // 6
[Function: Object] // 7

2번을 살펴보자 foo 객체에는 country 프로퍼티가 없고 Person.prototype객체도 마찬가지이다. 체이닝이 일어나도 결국 undefined
3번을 살펴보자 country라는 프로퍼티를 가진 객체로 변화시켰다. 그 결과로 이 객체에는 constructor가 존재하지 않는다. 일반 객체로 변화했기 때문이다. 일반 객체는 Object.prototype 객체로 프로토타입 체이닝이 발생하고 그 결과 그 constructor는 Object로 출력된다.
또한 생성된 bar 프로퍼티도 새로 변경된 프로토타입 객체를 가리킨다. 그렇기 때문에 서로 다른 결과값이 발생하는 것이다.

디폴트

주의할 점

프로토타입은 특정 프로퍼티를 읽거나, 메서드를 실행할 때 발생한다. 다른 객체에 있는 특정 프로퍼티를 쓰려고 할 때에는 프로토타입 체이닝이 동작하지 않고 동적으로 프로퍼티를 생성한다.

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

Person.prototype.country = 'Republic of Korea';

var foo = new Person('foo');
var bar = new Person('bar');
console.log(foo.country);
console.log(bar.country);
foo.country = 'USA';

console.log(foo.country);
console.log(bar.country);

출력결과

Republic of Korea
Republic of Korea
USA
Republic of Korea

foo.country 값에 ‘USA’를 지정하면 foo 객체 자체에 country 프로퍼티가 동적으로 생성된다. 그래서 bar 객체는 프로토타입 체이닝을 거쳐 ‘Republic of korea’가 출력된다.

다음 시간에는 실행 컨텍스트와 클로저에 대해 알아보겠습니다!