prototype

2025. 5. 24. 17:45Coding Study/Javascript

 

1. 프로토타입의 개념 이해

 1) 상속과 프로토 타입

상속은 객체지향 프로그래밍의 핵심 개념으로 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용

이를 통해 불필요한 중복을 제거 한다.

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
  this.getArea = function () {
    // Math.PI는 원주율을 나타내는 상수다.
    return Math.PI * this.radius ** 2;
  };
}

// 반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성
const circle2 = new Circle(2);

// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직하다.
console.log(circle1.getArea === circle2.getArea); // false

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

 

동일한 메서드가 중복 생성 된다. 생성된 모든 인스턴스가 동일한 메서드를 중복 소유하는 것은 메모리를 불필요하게 낭비 한다.

 

 

< 상속을 통해 불필요한 중복 제거 방법 >

 

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가한다.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function () {
  return Math.PI * this.radius ** 2;
};

// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
// 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

 

 

 

 

 

 

 

  2) constructor, prototype, instance

 

 

 

new 연산자로 Constructor 를 호출하면 instance 가 만들어 지는데, 이 instance 의 생략 가능한 프로퍼티인 __proto__는

Constructor 의 prototype 을 참조한다.

 

생성자 함수의 prototype에 어떤 매서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 사용할 수 있다.

 

 

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

let person = new Person("John");
console.log(person.__proto__.getName()); // undfined
console.log(person.getName());           // John

 

 

 

< __proto__ 와 Prototype 의 관계 >

 배열 리터럴과 Arry 의 관계

 let arr = [1, 2];
 console.dir(arr);
 console.dir(Array);

 

첫번째 출력 결과는 arr변수를 출력한 결과

  - 생성자 함수 Arry를 원형으로 삼아서 생성되었다는 것을 알 수 있다.

  - length 가 2이다.

  - 인덱스의 값이 1, 2 이다.

  - __proto__를 열어보면 배열에서 사용가능한 메서드 들이 있다.

 

 

arr의 __proto__ ([[Prototype]] , 와 Arry 의 prototype 의 매서드가 동일하게 들어 있다.

 

두번째 출력 결과는 생성자 함수 Arry 를 출력 한 결과이다.

  - 함수라는 의미의 f 가 표시되어 있다.

  - Arry 함수의 정적메서드 from , isArry ... 등등 이 있다.

  - Prototpye 의 내용에는 arr 변수의 __proto__ 와 동일한 내용이 들어가 있다.  

Array 를 new 연산자로 호출해서 생성하든 그냥 배열 리터럴로 생성하든 instance [1,2]가 만들어진다.

 

이 이스턴스의 __proto__ 는 Array.protorype 을 참조 한다.

 

__proto__는 생략 가능 하도록 설계 되어 있기 때문에 push, pop, forEach 같은 메서드를 자신의 것 처럼 호출 할 수 있다.

 

Array prototype 프로퍼티 내부에 있지 않은 매서드는 인스턴스가 호출 할 수 없다. 

 

 

 

3) Constructor 프로퍼티

prototype 내부에는 constructor 라는 프로퍼티 가 있다.

인스턴스의 __proto__는 prototype 을 가리키기 때문에 동일하게 가지고 있다.

이 constructor 프로퍼티는 원래의 생성자 함수를 참조한다.

const arr = [1, 2];

console.log(Array.prototype.constructor === Array); //true

console.log(arr.__proto__.constructor === Array); //true

console.log(arr.constructor === Array);//true

 

 

 

계속 해서 참조에 참조로 순환된다.

console.log(
arr.__proto__.constructor.prototype.constructor.prototype.constructor === Array
);    //true

 

 

< Constructor 변경 >

 

Constructor 변경 시 참조하는 대상만 변경 될 뿐 이미 만들어진 인스턴스의 원형이 바뀌지는 않는다.

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

let person1 = new Person("John");
console.log(person1.__proto__.getName());  //undefined
console.log(person1.getName());            //John

function NewConstructor() {
  this.name = "John";
}

person1.constructor = NewConstructor;
console.log(person1.constructor.name);            //NewConstructor
console.log(person1 instanceof NewConstructor);   //false person1 의 프로토타입 체인이 아니다.
console.log(person1 instanceof Person);           //true  person1 의 프로토타입 체인에 있다

*instanceof 연산자 : 우변의 생성자 함수가 좌변의 프로토타입 체인상에 존재하면 true를 반환

 

< constructor 접근방법>

 constructor 접근 방법에는 여러가지가 있다. 

const Person = function(name) {
  this.name = name;
};
let p1 = new Person('사람1');             // Person { name: "사람1" } true
let p1Proto = Object.getPrototypeOf(p1);
let p2 = new Person.prototype.constructor('사람2'); // Person { name: "사람2" } true
let p3 = new p1Proto.constructor('사람3'); // Person { name: "사람3" } true
let p4 = new p1.__proto__.constructor('사람4'); // Person { name: "사람4" } true
let p5 = new p1.constructor('사람5'); // Person { name: "사람5" } true


[p1, p2, p3, p4, p5].forEach(function (p) {
  console.log(p, p instanceof Person);
});

 *getPrototpyeOf(obj) obj 의 prototype 을 반환한다.

 

< 참고 그림 circle2 를 p로 대체 >

 

2. 프로토타입 체인

 1) 메서드 오버라이드

const Person = (function () {
  // 생성자 함수
  function Person(name) {
    this.name = name;
  }
  // 프로토타입 메서드
  Person.prototype.sayHello = function () {
    console.log(`Hi! My name is ${this.name}`);
  };
  // 생성자 함수를 반환
  return Person;
}());

const me = new Person('Lee');

// 인스턴스 메서드
me.sayHello = function () {
  console.log(`Hey! My name is ${this.name}`);
};

// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee

 

프로토 타입이 소유한 프로퍼티를 프로토타입 프로퍼티

인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티

 

프로토 타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에서 추가하면 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.

 

 

이상태에서 prototype 에 있는 메서드에 접근 하게 되면 undefined 가 된다.

console.log(me.__proto__.name);  // undefined

 

 

하지만 프로퍼티를 지정하면 그 값을 출력 할 수 있다.

Person.prototype.name = "Kim";
me.__proto__.sayHello();         // Hi! My name is Kim

 

 

 

2) 프로토 타입 체인

console.log({ a : 1 });

 

객체의 내부구조를 출려해서 보게 되면 __proto__ 내부에 또 __proto__가 들어있는 것을 볼 수 있다.

 

이 트리구조를 그림과 같이 표현해 보면 아래와 같이 볼 수 있다. 

이 결과의 의미는 만약 객체가 메서드를 호출 하게 된다면 자신의 프로퍼티를 먼저 검색하고 없으면, 상위 프로토타입의 메서드를

검색하고 그래도 없으면 그 위의 프로토타입의 메서드를 검색해서 있으면 그 메서드를 실행한다.

 

'Coding Study > Javascript' 카테고리의 다른 글

배열  (0) 2025.08.29
클래스  (2) 2025.06.02
콜백 함수  (0) 2025.05.10
실행 컨택스트  (0) 2025.04.23
자바스크립트 var, let, const 키워드  (0) 2025.04.18