songin.dev님의 블로그

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

Developer/Langauge

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

songin.dev 2022. 7. 18. 11:44
728x90

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

자바스크립트에서는 클래스 개념이 없다. 

대신에 객체 리터럴이나 생성자 함수로 객체를 생성한다. 이렇게 생성된 객체의 부모 객체가 '프로토타입' 객체다.

즉, 상속 개념과 마찬가지로 자식 객체는 부모 객체가 가진 프로퍼티 접근이나 메서드를 상속받아 호출하는 것이 가능하다.

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

이러한 링크를 암묵적 프로포타입 링크(implicit prototype link)라고 부르며 이러한 링크는 모든 객체의 [[Prototype]] 프로퍼티에 저장된다.

여기서 주의할 점은 함수 객체의 prototype 프로퍼티와 객체의 숨은 프로퍼티인 [[Prototype]] 링크를 구분해야 하는 것이다.

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

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

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

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

Person() 생성자 함수는 prototype 프로퍼티로 자신과 링크된 프로토타입 객체 Person.prototype를 가리킨다.

그리고 자바스크립트의 객체 생성 규칙에 의하면 Person() 생성자 함수로 생성된 foo 객체는 Person() 함수의 프로토타입 객체를 [[Prototype]] 링크로 연결한다. 결국, prototype 프로퍼티나 [[Prototype]] 링크는 같은 프로토타입 객체를 가리키고 있다.

 

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

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

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();

/*
My Name is foo
true
false
Uncaught TypeError: Object #<Object> has no method 'sayNickName'
*/

myObject는 name 프로퍼티와 sayName() 메서드를 가진 객체이다.

출력 결과를 보면 sayNickName() 메서드는 myObject의 메서드가 아니므로 에러가 발생한다.

hasOwnProperty()메서드는 이 메서드를 호출한 객체에 인자로 넘긴 문자열 이름의 프로퍼티나 메서드가 있는지 체크하는 자바스크립트 표준 API 함수다. 따라서 true와 false 값으로 정상적으로 출력됐다.

 

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

 

Object.prototype 객체는 자바스크립트 모든 객체의 조상 역할을 하는 객체로서, 자바스크립트 모든 객체가 호출할 수 있는 toString(), hasOwnProterty() 등과 같은 표준 메서드를 제공하고 있다.

 

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

생성자 함수로 객체를 생성하는 경우는 객체 리터럴 방식과 약간 다른 프로토타입 체이닝이 이뤄진다.

// 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);

프로토타입 체이닝의 종점

자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점이다.

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

 

기본 데이터 타입 확장

자바스크립트의 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, 이들의 프로토타입인 Number.prototype, String.prototype, Array.prototype 등에 정의되어 있다.

물론 이러한 기본 내장 프로토타입 객체 또한 Object.prototype을 자신의 프로토타입으로 가지고 있어서 프로토타입 체이닝으로 연결된다.

자바스크립트는 Object.prototype, String.prototype 등과 같이 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드들을 추가하는 것을 허용한다.

String.prototype.testMethod = function () {
  console.log('This is the String.prototype.testMethod()');
};

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

console.dir(String.prototype);

다양한 String.prototype과 함께 추가된 testMethod는 문자열에서 API처럼 사용할 수 있다. 

 

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

함수가 생성될 때, 자신의 prototype 프로퍼티에 연결되는 프로토타입 객체는 디폴트로 constructor 프로퍼티만을 가진 객체다.

당연히 프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다. 

그리고 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.

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

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

// foo.sayHello(); // 주석 제거시 Error 발생

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

foo.sayHello(); // Hello

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

프로토타입 객체는 메서드를 가질 수 있다.

메서드 호출 패턴에서의 this는 그 메서드를 호출한 객체에 바인딩된다.

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

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

var foo = new Person('foo');

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

Person.prototype.name = 'person';

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

디폴트 프로토타입은 다른 객체로 변경이 가능하다.

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

자바스크립트에서는 이렇게 함수를 생성할 때 해당 함수와 연결되는 디폴트 프로트타입 객체를 다른 일반 객체로 변경하는 것이 가능하다.

여기서 주의할 점은 생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 [[Prototype]] 링크를 연결한다. 이에 반해 생성자 함수의 프로토타입이 변경되기 이전에 생성된 객체들은 기존 프로토타입 객체로의 [[Prototype]] 링크를 그대로 유지한다.

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

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

// 디폴트 프로토타입 객체 변경
Person.prototype = {
  country: 'korea',
};
console.log(Person.prototype.consturctor);

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

/*
Person(name)
undefined
Object()
undefined
korea
Person(naem)
Object()
*/

객체의 프로퍼티 읽기나 메서드를 실행할 때만 프로토타입 체이닝이 동작한다.

객체의 특정 프로퍼티를 읽으려고 할 때, 프로퍼티가 해당 객체에 없는 경우 프로토타입 체이닝이 발생한다.

반대로 객체에 있는 특정 프로퍼티에 값을 쓰려고 한다면 이때는 프로토타입 체이닝이 일어나지 않는다.

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

Person.prototype.country = '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);

 

 

- 본 포스팅은 "인사이드 자바스크립트" 도서를 기반으로 작성한 글입니다.

반응형
Comments