본문 바로가기

개발/연구

[js 기본] 프로토타입

JS는 기본적으로 프로토타입 기반 객체지향 프로그래밍 언어예요. 일단 JS는 클래스라는 개념이 없고, 프로토타입 원형을 복사하고 덧붙여서 확장해나가는 개념이예요. 상속과 유사하죠. 프로토타입 기반의 언어이기 때문에 JS 프로토타입의 개념을 잘 이해할 필요가 있어요. 

모든 객체는 __proto__ 프로퍼티를 가지고 있어요. 이 것은 마치 상속과 유사해요. 모두 가지고 있죠. Prototype을 살펴 볼까요?

이 후부터는 __proto__ 프로퍼티를 [[Prototype]]으로 호칭하고, prototypePrototype이라고 호칭할게요.

1. Prototype

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

const student = {
  name: 'Lee',
  score: 90
}

const foo = new Person('Lee');

console.log(Person.__proto__);
console.log(student.__proto__);
console.log(foo.__proto__);

// output :
// [Function]
// {}
// Person {}

이와 같이 모두 [[Prototype]]을 가지고 있는 것을 볼 수 있습니다. 여기서 Output이 모두 다른데. 이것은 아래 4장에서 살펴 보도록 할게요.

2. Constructor Property

Constructor객체의 입장에서 자신을 생성한 객체를 가리켜요.

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

const foo = new Person('Lee')

console.log(Person.prototype.constructor === Person);
console.log(foo.constructor === Person);
console.log(Person.constructor === Function);

// output :
// true
// true
// true

이와 같이 Person의 프로토타입(Function 객체)이 생성한 객체(Constructor)는 Person 객체가 생성했고, foo 객체는 Person의 생성자 함수에 의해 생성 되었고, Person 객체는 Function 객체에 의해 생성되었어요.

이해를 돕기위한 코드를 하나 작성해봤어요. 원시타입인 String 객체로 좀 더 보기 쉽게 보여드릴게요.

  const str = "Hello Miracle!";

  console.log(str.__proto__.constructor);
  console.log(str.__proto__.__proto__.constructor);
  
  /*
  output :
  [Function: String]
  [Function: Object]
  */

이렇게, 프로토타입을 거슬러 올라가면 상위객체를 만나볼 수 있어요.

조금 더 자세히 들여다볼까요?

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

const foo = new Person('Lee');

// foo 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(foo)
console.log()

console.log("foo.constructor", foo.constructor);
console.log("foo.__proto__", foo.__proto__);
console.log()

console.log("foo.__proto__.constructor", foo.__proto__.constructor);
console.log("foo.constructor.__proto__", foo.constructor.__proto__);
console.log()

console.log("foo.__proto__.constructor.__proto__", foo.__proto__.constructor.__proto__);
console.log("foo.constructor.__proto__.constructor", foo.constructor.__proto__.constructor);
console.log()

console.log("foo.__proto__.constructor.__proto__.constructor", foo.__proto__.constructor.__proto__.constructor);
console.log("foo.constructor.__proto__.constructor.__proto__", foo.constructor.__proto__.constructor.__proto__);
console.log()

console.log("foo.__proto__.constructor.__proto__.constructor.__proto__", foo.__proto__.constructor.__proto__.constructor.__proto__);
console.log("foo.constructor.__proto__.constructor.__proto__.constructor", foo.constructor.__proto__.constructor.__proto__.constructor);
console.log()

/*
output :
Person { name: 'Lee' }

foo.constructor [Function: Person]
foo.__proto__ Person {}

foo.__proto__.constructor [Function: Person]
foo.constructor.__proto__ [Function]

foo.__proto__.constructor.__proto__ [Function]
foo.constructor.__proto__.constructor [Function: Function]

foo.__proto__.constructor.__proto__.constructor [Function: Function]
foo.constructor.__proto__.constructor.__proto__ [Function]

foo.__proto__.constructor.__proto__.constructor.__proto__ [Function]
foo.constructor.__proto__.constructor.__proto__.constructor [Function: Function]
*/

 

 

[[Prototype]]과 constructor를 반복해보았습니다. [[Prototype]]는 생성된 자신의 상위 객체. constructor는 자신을 호출한 객체예요.

3. Property : __proto__ VS prototype

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

const foo = new Person('Lee');

console.log(foo.prototype)
console.log(foo.__proto__)

/*
output :
undefined
Person {}
*/

Prototype은 모두가 갖고 있지 않아요. 생성된 오브젝트를 대상으로 prototype를 호출하면 정의되지 않은 값이지요. 그 반면, __proto__는 모든 객체가 가지고 있는 프로퍼티입니다.

const foo = new Person('Lee');

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

/*
output :
Person {}
[Function: Person]
*/

Prototype은 함수 객체만 가지고 있으며, 함수 객체가 생성자로 사용될 때(즉, 클래스를 활용하여 오브젝트를 생성할 때.) 이 함수를 통하여 생성될 객체의 부모 역할을 하는 객체를 가리켜요.

4. Prototype Chain

Prototype Chain은 [[Prototype]]과 링크가 되어있는 것을 뜻해요. 

var student = {
  name: 'Lee',
  score: 90
}

// Object.prototype.hasOwnProperty()
console.log(student.hasOwnProperty('score')); // true

student는 hasOwnProperty라는 메서드를 갖고있지 않지만 사용이 가능하지요. 가능한 이유는 student의 [[Prototype]]이 가라키는 링크를 거슬러 올라가, Object.prototype의 메소드 중 hasOwnProperty가 있기 때문이예요. 이것을 Prototype Chain이라고 부릅니다.

4.1 객체리터럴 방식의 프로토타입 체인

var person = {
  name: 'Lee',
  gender: 'male',
  sayHello: function(){
    console.log('Hi! my name is ' + this.name);
  }
};

console.dir(person);

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

*객체 리터럴 방식의 객체는 [[Prototype]]이 Object.prototype과 같은 것을 확인할 수 있어요. 여기서 Object.prototype는 *프로토타입 체인의 종점(End of prototype chain)이라고 표현을 하기도 합니다. 

 

*End of prototype chain : 모든 객체의 부모 객체. 자바에서의 Object와 비슷해요.

*객체 리터럴 : 중괄호를 이용해서 객체를 생성하는 방식.(person 참고)

 

 

출처 : https://poiemaweb.com/js-prototype

위 그림을 참고하면, person 객체의 [[Prototype]]은 Object.prototype인 것을 알 수 있습니다. 즉 부모가 프로토타입 체인의 종점입니다.

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

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHello = function(){
    console.log('Hi! my name is ' + this.name);
  };
}

var foo = new Person('Lee', 'male');

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

console.log(foo.__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

생성자 함수로 선언한 foo의 [[Prototype]]은 Person.prototype이고, Person.prototype의 [[Prototype]]은 Object.prototype인 것을 알 수 있어요. 부가적으로 모든 함수 정의 방식은 결국 Function() 생성자 함수를 통하여 함수 객체를 생성하는 것도 알 수 있죠.

출처 : https://poiemaweb.com/js-prototype

5. 프로토타입 객체 값 추가

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

var foo = new Person('Lee');

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

foo.sayHello();

프로토타입은 결국 객체예요. 따라서 프로퍼티를 추가할 수도 있고, 삭제할 수도 있죠. 이렇게 갱신된 프로퍼티는 프로토타입으로 만들어졌던 모든 오브젝트에 반영이 된답니다.

 

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

객체의 프로퍼티를 참조하는 경우와 해당 객체에 프로퍼티가 없는 경우에 프로토타입 체인이 동작해요.

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

Person.prototype.gender = 'male'; // ①

var foo = new Person('Lee');
var bar = new Person('Kim');

console.log(foo.gender); // ① 'male'
console.log(bar.gender); // ① 'male'

// 1. foo 객체에 gender 프로퍼티가 없으면 프로퍼티 동적 추가
// 2. foo 객체에 gender 프로퍼티가 있으면 해당 프로퍼티에 값 할당
foo.gender = 'female';   // ②

console.log(foo.gender); // ② 'female'
console.log(bar.gender); // ① 'male'

https://poiemaweb.com/js-prototype

 

참고 :
https://poiemaweb.com/js-prototype
https://velog.io/@jimmyjoo/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4%EB%A6%AC%ED%84%B0%EB%9F%B4