프론트엔드/JavaScript

(JavaScript) 프로토타입과 프로토타입 체인

그린티_ 2026. 2. 6. 12:07
반응형

서론

JavaScript로 개발하다 보면 Array.prototype.map, Object.prototype.toString 같은 표현을 자주 보게 됨
처음엔 "prototype이 뭔데 여기저기 붙어있지?" 싶었고, class 문법만 쓰다 보니 프로토타입을 깊게 생각해본 적이 없었음

근데 면접 준비하면서 "JavaScript는 프로토타입 기반 언어입니다"라는 말이 계속 나오고, class도 결국 프로토타입의 문법적 설탕(syntactic sugar)이라는 걸 알게 됨
"그러면 진짜 상속은 어떻게 동작하는 거지?" 싶어서 프로토타입부터 프로토타입 체인까지 정리해봄


본론

1. 프로토타입이 뭔데?

JavaScript에서 모든 객체는 숨겨진 링크를 가지고 있음. 이 링크가 가리키는 객체를 프로토타입(Prototype)이라고 함

const arr = [1, 2, 3];

// arr에 직접 map을 정의한 적 없는데 쓸 수 있음
arr.map(x => x * 2); // [2, 4, 6]

// 왜? arr의 프로토타입인 Array.prototype에 map이 있기 때문
console.log(arr.__proto__ === Array.prototype); // true
  • arr이라는 배열 객체에는 map 메서드가 직접 없음
  • 하지만 arr의 프로토타입인 Array.prototypemap이 정의되어 있어서 사용 가능
  • 이렇게 자기한테 없으면 프로토타입에서 찾는 것이 프로토타입의 핵심

2. __proto__ vs prototype 차이 (헷갈리는 부분!)

프로토타입을 공부하면 __proto__prototype 두 가지가 나와서 처음에 엄청 헷갈림

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

Person.prototype.greet = function () {
  return `안녕, 나는 ${this.name}`;
};

const greentea = new Person('그린티');

// __proto__: 인스턴스가 자신의 프로토타입을 가리키는 링크
console.log(greentea.__proto__ === Person.prototype); // true

// prototype: 생성자 함수가 가지고 있는 프로퍼티 (인스턴스의 프로토타입이 될 객체)
console.log(Person.prototype); // { greet: [Function], constructor: Person }

핵심 정리:

구분 누가 가지고 있나 역할
__proto__ 모든 객체 자신의 프로토타입(부모)을 가리키는 링크
prototype 함수(생성자) new로 생성된 인스턴스의 프로토타입이 될 객체
  • __proto__는 모든 객체가 가지고 있고, 해당 객체의 부모(프로토타입)를 가리킴
  • prototype함수만 가지고 있고, 그 함수로 new를 호출했을 때 만들어지는 인스턴스의 __proto__가 가리킬 대상

참고: __proto__는 비표준이라 실무에서는 Object.getPrototypeOf()를 사용하는 것이 권장됨


3. 프로토타입 체인 (Prototype Chain)

객체에서 프로퍼티를 찾을 때, 자기 자신 → 프로토타입 → 프로토타입의 프로토타입 → ... 이렇게 연쇄적으로 탐색하는 것을 프로토타입 체인이라고 함

function Animal(type) {
  this.type = type;
}

Animal.prototype.breathe = function () {
  return `${this.type}이(가) 숨을 쉼`;
};

function Dog(name) {
  Animal.call(this, '개'); // Animal의 생성자 호출
  this.name = name;
}

// Dog의 프로토타입을 Animal의 인스턴스로 설정 → 체인 연결
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  return `${this.name}이(가) 멍멍!`;
};

const myDog = new Dog('초코');

console.log(myDog.bark());    // '초코이(가) 멍멍!' → Dog.prototype에서 찾음
console.log(myDog.breathe()); // '개이(가) 숨을 쉼' → Animal.prototype에서 찾음
console.log(myDog.toString()); // '[object Object]' → Object.prototype에서 찾음

탐색 순서:

  1. myDog 자신에게 해당 프로퍼티가 있는지 확인
  2. 없으면 → myDog.__proto__ (= Dog.prototype)에서 확인
  3. 없으면 → Dog.prototype.__proto__ (= Animal.prototype)에서 확인
  4. 없으면 → Animal.prototype.__proto__ (= Object.prototype)에서 확인
  5. 없으면 → Object.prototype.__proto__null탐색 종료, undefined 반환
myDog → Dog.prototype → Animal.prototype → Object.prototype → null

이게 바로 프로토타입 체인. JavaScript의 상속은 이 체인을 통해 이루어짐


4. class 문법은 프로토타입의 설탕

ES6의 class는 프로토타입을 더 보기 좋게 쓰는 문법적 설탕(syntactic sugar)일 뿐임

// ES6 class 문법
class Animal {
  constructor(type) {
    this.type = type;
  }

  breathe() {
    return `${this.type}이(가) 숨을 쉼`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super('개');
    this.name = name;
  }

  bark() {
    return `${this.name}이(가) 멍멍!`;
  }
}

const myDog = new Dog('초코');
// 내부적으로는 프로토타입 기반으로 동작함
console.log(typeof Dog); // 'function' (class가 아님!)
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true

// class로 만들어도 프로토타입 체인은 동일
// myDog → Dog.prototype → Animal.prototype → Object.prototype → null
  • class는 보기 좋을 뿐, 내부적으로는 똑같이 프로토타입 체인을 사용
  • extends는 프로토타입 체인을 연결해주는 것이고, superAnimal.call(this, ...)와 비슷한 역할

5. 프로토타입 관련 유용한 메서드

const obj = { a: 1 };

// 1. Object.create(): 특정 프로토타입을 가진 새 객체 생성
const child = Object.create(obj);
console.log(child.a); // 1 (프로토타입 체인으로 접근)

// 2. Object.getPrototypeOf(): 프로토타입 확인 (__proto__ 대신 권장)
console.log(Object.getPrototypeOf(child) === obj); // true

// 3. hasOwnProperty(): 자기 자신의 프로퍼티인지 확인 (프로토타입 체인 X)
console.log(child.hasOwnProperty('a')); // false (프로토타입에서 온 것)
child.a = 2;
console.log(child.hasOwnProperty('a')); // true (자기 자신의 것)

// 4. instanceof: 프로토타입 체인에 특정 생성자가 있는지 확인
console.log(myDog instanceof Dog);    // true
console.log(myDog instanceof Animal); // true
console.log(myDog instanceof Object); // true

6. 프로토타입 오염 주의

프로토타입은 공유되기 때문에, 잘못 수정하면 모든 인스턴스에 영향이 감

// ❌ 이렇게 하면 안 됨 — 모든 배열에 영향!
Array.prototype.first = function () {
  return this[0];
};

const arr1 = [1, 2, 3];
const arr2 = ['a', 'b', 'c'];

console.log(arr1.first()); // 1
console.log(arr2.first()); // 'a'

// 편해 보이지만...
for (const key in arr1) {
  console.log(key); // 0, 1, 2, 'first' ← 의도치 않은 프로퍼티!
}
// ❌ 보안 위험 — 프로토타입 오염 공격
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
// Object.assign({}, malicious)를 하면 Object.prototype이 오염될 수 있음

// ✅ 방어: Object.create(null)로 프로토타입 없는 객체 사용
const safeObj = Object.create(null);
// safeObj에는 __proto__도 없어서 오염 불가

주의점 정리:

  • 빌트인 프로토타입(Array.prototype, Object.prototype 등)을 절대 수정하지 말 것
  • 외부 데이터를 객체에 병합할 때 프로토타입 오염 주의
  • 안전한 빈 객체가 필요하면 Object.create(null) 사용

7. 면접 예상 질문 & 답변

Q1. 프로토타입이란 무엇인가요?

JavaScript에서 모든 객체는 다른 객체에 대한 숨겨진 참조(proto)를 가지고 있으며, 이 참조가 가리키는 대상 객체를 프로토타입이라고 합니다. 객체에서 프로퍼티를 찾을 때 자신에게 없으면 프로토타입에서 탐색합니다.

Q2. 프로토타입 체인이란?

객체의 프로퍼티를 찾을 때 자기 자신 → 프로토타입 → 프로토타입의 프로토타입 순으로 연쇄적으로 탐색하는 메커니즘입니다. 최상위인 Object.prototype까지 올라가도 없으면 undefined를 반환합니다.

Q3. __proto__prototype의 차이는?

__proto__는 모든 객체가 가지고 있으며 자신의 프로토타입을 가리키는 링크입니다. prototype은 함수(생성자)만 가지고 있으며, new 키워드로 생성된 인스턴스의 __proto__가 참조할 객체입니다.

Q4. class는 프로토타입과 어떤 관계인가요?

ES6의 class는 프로토타입 기반 상속을 보기 좋게 작성하기 위한 문법적 설탕(syntactic sugar)입니다. 내부적으로는 동일하게 프로토타입 체인을 사용하며, typeof로 확인하면 class도 function입니다.

Q5. hasOwnProperty와 in 연산자의 차이는?

hasOwnProperty는 객체 자신이 직접 가진 프로퍼티만 확인하고, in 연산자는 프로토타입 체인 전체를 탐색하여 해당 프로퍼티가 존재하는지 확인합니다.


결론

프로토타입은 JavaScript 상속의 근본 원리

  • 프로토타입: 모든 객체가 가지고 있는 부모 객체에 대한 참조
  • 프로토타입 체인: 자신 → 부모 → 부모의 부모 순으로 프로퍼티를 탐색하는 메커니즘
  • __proto__ vs prototype: 인스턴스의 링크 vs 생성자 함수의 프로퍼티
  • class: 프로토타입의 문법적 설탕, 내부 동작은 동일
개념 설명
프로토타입 모든 객체가 참조하는 부모 객체
프로토타입 체인 프로퍼티 탐색 시 부모를 타고 올라가는 메커니즘
__proto__ 인스턴스 → 프로토타입을 가리키는 링크
prototype 생성자 함수가 가진 프로퍼티 (인스턴스의 프로토타입이 될 객체)
Object.create() 특정 프로토타입을 가진 객체 생성
class / extends 프로토타입 기반 상속의 문법적 설탕

React에서 class 문법 대신 함수형 컴포넌트를 많이 쓰면서 프로토타입을 직접 다룰 일은 줄었지만, instanceof 체크나 메서드 탐색 과정을 이해하려면 프로토타입 체인을 알아야 한다는 걸 느꼈음
앞으로 라이브러리 코드를 읽거나 디버깅할 때 프로토타입 체인을 의식하면서 봐야겠음!

반응형