프로토타입

mandoo·2022년 11월 1일
0

JavaScript

목록 보기
3/7

1. 프로토타입(Prototype) 객체

JavaScript는 프로토타입 기반 객체 지향 프로그래밍 언어이다. Java나 C++과 같은 클래스 기반 객체지향 언어와는 달리 프로토타입 기반 객체 지향 프로그래밍 언어는 클래스 없이(class-less)도 객체를 생성할 수 있다.
JavaScript의 모든 객체는 자신이 부모 역할을 담당하는 객체와 연결되어 있어 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있다. 이러한 부모 객체를 프로토타입(Prototype)객체 또는 줄여서 프로토타입이라고 한다.

프로토타입 객체는 생성자 함수에 의해 생성된 각각의 객체에 공유 프로퍼티를 제공하기 위해 사용한다.

var person = {
      name: "anne",
      gender: "female",
      age: 16,
 };

console.log(person.hasOwnProperty); // true
console.dir(person);

JavaScript의 모든 객체는 [[Prototype]]이라는 내부 슬롯(internal slot)를 가진다. [[Prototype]]의 값은 프로토타입 객체이며 __proto__accesor property로 접근할 수 있다. __proto__ 프로퍼티에 접근하면 내부적으로 Object.getPrototypeOf가 호출되어 프로토타입 객체를 반환한다.

person1객체는 __proto__ 프로퍼티로 자신의 부모 객체(프로토타입 객체)인 Object.prototype을 가리키고 있다.

var person = {
      name: "anne",
      gender: "female",
      age: 16,
 };

 console.log(person.__proto__ === Object.prototype); // true

프로토타입은 객체를 생성할 때 결정된다. 결정된 프로토타입 객체는 다른 임의의 객체로 변경할 수 있는데 이는 부모 객체인 프로토타입을 동적으로 변경할 수 있다는 것을 의미한다. 이 특성을 사용하여 객체 지향 언어의 상속을 구현할 수 있다.

2. [[Prototype]] vs prototype 프로퍼티

모든 객체는 자신의 프로토타입 객체를 가리키는 [[Prototype]] 내부 슬롯을 가지며 상속을 위해 사용된다.
함수도 객체이므로 [[Prototype]] 내부 슬롯을 갖는다. 그러나 함수 객체는 다른 객체와 달리 prototype 프로퍼티도 소유하고 있다.

function Person(name) {
  this.name = name;
  
var person1 = new Person("anne");
  
 console.dir(Person); // 프로토타입 프로퍼티가 있다.
 console.dir(person1); // 프로토타입 프로퍼티가 없다.
console.log(Person.__proto__ === Function.prototype); // true
  • [[Prototype]]
    • 함수를 포함한 모든 객체가 가지고 있는 내부 슬롯이다.
    • 객체 자신의 부모 객체인 프로토타입 객체를 가리키며 함수 객체의 경우 Function.prototype을 가리킨다.
console.log(Person.prototype === person1.__proto__); // true
  • prototype 프로퍼티
    • 함수 객체만 가지고 있는 프로퍼티이다.
    • 함수 객체가 생성자로 사용될 때, 이 함수를 통해 생성될 객체의 부모 역할을 하는 객체(프로토타입 객체)를 가리킨다.

3. constructor 프로퍼티

프로토타입 객체는 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 객체 자신(프로토타입 객체의 자식 객체)을 생성한 객체를 가리킨다.

function Person(name) {
  this.name = name;
  
var person1 = new Person("anne");
  
console.log(Person.prototype.constructor === Person); //true
console.log(person1.constructor === Person); //true
console.log(Person.constructor === Function); //true 

예를 들어 Person() 생성자 함수에 의해 생성된 객체를 person1이라 하면 person1 객체의 프로토타입 객체는 Person.prototype이다. 따라서 프로토타입 객체 Person.prototype의 constructor 프로퍼티는 Person() 생성자 함수를 가리킨다.

4. Prototype chain

Javascript는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [\Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례대로 검색한다. 이를 프로토타입 체인이라고 한다.

var person = {
  name: "anne",
  gender: "female",
  age: 16
};

console.log(person.hasOwnProperty('name')) // true

person 객체는 hasOwnProperty 메소드를 가지고 있지 않으므로 에러가 발생하여야 하나 정상적으로 결과가 출력되었다. 이는 person 객체의 [[Prototype]]이 가리키는 링크를 따라 perosn 객체의 부모 역할을 하는 프로토타입 객체(Object.prototype)의 메소드 hasOwnProperty를 호출하였기 때문에 가능한 것이다.

var person = {
  name: "anne",
  gender: "female",
  age: 16
};

console.log(person.hasOwnProperty('name')); // true
console.log(person.__proto__ === Object.prototype); // true
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // true

1) 객체 리터럴 방식으로 생성된 객체의 프로토타입 체인

객체 생성 방법은 3가지가 있다.

  • 객체 리터럴
  • 생성자 함수
  • Object() 생성자 함수
    객체 리터럴 방식은 내부적으로는 Object() 생성자 함수를 사용하여 객체를 생성한다. Object() 생성자 함수 역시 함수이므로 prototype 프로퍼티가 존재한다.
var person = {
  name: 'anne',
  gender: 'female',
  sayHi: function(){
    console.log('Hi! my name is ' + this.name);
  }
};

console.log(person.__proto__ === Object.prototype);   // ① true
console.log(Object.prototype.constructor === Object); // ② true
console.log(Object.__proto__ === Function.prototype); // ③ true
console.log(Function.prototype.__proto__ === Object.prototype); // ④ true

리터럴 방식으로 객체를 생성한 경우 그 객체의 프로토타입 객체는 Object.prototype이 된다.

2) 생성자 함수로 생성된 객체의 프로토타입 체인

생성자 함수로 객체를 생성하기 위해서는 우선 생성자 함수를 정의해야 한다.
JavaScript는 함수를 어떻게 정의하든 Function() 생성자 함수를 통해 함수 객체를 생성한다. 따라서 생성자 함수를 포함한 모든 함수 객체의 프로토타입 객체는 Function.prototype이다.

객체 생성 방법 3가지에 의해 생성된 객체의 prototype 객체는 아래와 같다.

객체 생성 방식JavaScript 엔진의 객체 생성인스턴스의 prototype 객체
객체 리터럴Object() 생성자 함수Object.prototype
Object() 생성자 함수Object() 생성자 함수Object.prototype
생성자 함수생성자 함수생성자 함수 이름.prototype
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHi = function(){
    console.log('Hi! my name is ' + this.name);
  };
}

var person1 = new Person('anne', 'female');

console.log(person1.__proto__ === Person.prototype);                // ① true
console.log(Person.prototype.__proto__ === Object.prototype);   // ② true
console.log(Person.prototype.constructor === Person);           // ③ true
console.log(Person.__proto__ === Function.prototype);           // ④ true
console.log(Function.prototype.__proto__ === Object.prototype); // ⑤ true

person1 객체의 프로토타입 객체 Person.prototype 객체와 Person() 생성자 함수의 프로토타입 객체인 Function.prototype의 프로토타입 객체는 Object.prototype 객체이다.

이는 객체 리터럴 방식이나 생성자 함수 방식이나 결국은 모든 객체의 부모 객체인 Object.prototype 객체에서 프로토타입 체인이 끝나기 때문이다. 이때 Object.prototype 객체를 프로토타입 체인의 종점(End of prototype chain)이라 한다.

5. 프로토타입 객체의 확장

프로토타입 객체도 객체이므로 프로퍼티를 추가 혹은 삭제할 수 있으며 변경된 프로퍼티는 즉시 프로토타입 체인에 반영된다.

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

var person1 = new Person('anne');

Person.prototype.sayHi = function(){
  console.log('Hi! my name is ' + this.name);
};

person1.sayHi(); // Hi! my name is anne

생성자 함수 Person은 프로토타입 객체 Person.prototype과 prototype 프로퍼티에 의해 바인딩되어있다.
위 코드에서 Person.prototype 객체에 메소드 sayHi를 추가하였다. sayHi 메소드는 프로토타입 체인에 반영되어 person1이 부모객체인 Person.prototype의 메소드를 사용할 수 있게 되었다.

6. 프로토타입 객체의 변경

프로토타입 객체는 객체를 생성할 때 결정되지만 다른 임의의 객체로 변경할 수 있다. 이는 부모 객체인 프로토타입 객체를 동적으로 변경할 수 있다는 것을 의미한다. 이를 통해 객체의 상속을 구현할 수 있다.

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

var person1 = new Person('anne')

Person.prototype = { gender: 'female' };

var person2 = new Person('diana');

console.log(person1.gender); // female
console.log(person2.gender); // female

console.log(person1.constructor); // ① person(name)
console.log(person2.constructor); // ② Object()

① 프로토타입 객체 변경 전 Person.prototype의 constructor 프로퍼티는 Person() 생성자 함수를 가리킨다.
② 프로토타입 객체 변경 후 Person() 생성자 함수의 Prototype 프로퍼티가 가리키는 프로토타입 객체를 일반 객체로 변경하면서 Person.prototype.constructor 프로퍼티가 삭제되었다. 따라서 프로토타입 체인에 의해 person1.constructor의 값은 Object.prototype.consturctor 즉 Object() 생성자 함수가 된다.

7. 프로토타입 체인 동작 조건

객체의 프로퍼티를 참조할 때 해당 객체에 프로퍼티가 존재하지 않는 경우 프로토타입 체인이 동작한다.
객체의 프로퍼티에 값을 할당하는 경우에는 프로토타입 체인이 동작하지 않는다. 객체에 해당 프로퍼티가 있는 경우 값을 재할당하고 해당 프로퍼티가 없는 경우에는 객체에 프로퍼티를 추가하기 때문이다.

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

Person.prototype.gender = "female";

var person1 = new Person("anne");
var person2 = new Person("gilbert");

console.log(person1.gender); //female
console.log(person2.gender); // female

person2.gender = "male";

console.log(person1.genrder); //female
console.log(person2.genrder); // male

person2 객체의 gender 프로퍼티에 값을 할당하면 프로토타입 체인에 의해 Person.prototype 객체의 gender 프로퍼티가 수정되는 것이 아니라 ② person2 객체에 프로퍼티를 새로 추가하게 된다.

8. 참고 문서

poiemaweb: JavaScript 프로토타입

profile
매일 조금씩이라도 꾸준히

0개의 댓글