1. 개요

class 키워드는 ES2015에 들어왔지만 자바스크립트의 상속 메커니즘 자체는 바뀌지 않았다. 여전히 프로토타입 체인이다. class는 그 위에 올라간 문법 설탕(syntactic sugar)이다. 프로토타입이 실제로 뭘 하는지 알면 class를 쓸 때도, class가 아닌 오래된 코드를 읽을 때도 헷갈리지 않는다.


2. 핵심 내용

2-1. 모든 객체는 프로토타입을 가진다

자바스크립트 객체는 자기 프로퍼티를 못 찾으면 자신의 프로토타입에서 찾는다. 거기도 없으면 그 프로토타입의 프로토타입에서 찾는다. 이 연쇄가 프로토타입 체인이다.

const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.eats;                    // true, 프로토타입에서 찾아온다
rabbit.hasOwnProperty("eats");  // false

2-2. 함수의 prototype 프로퍼티와 new

함수는 prototype이라는 프로퍼티를 기본으로 가진다. new 함수()로 객체를 만들면, 그 객체의 내부 프로토타입 링크가 함수의 prototype으로 연결된다.

function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return `${this.name} 소리`; };
 
const dog = new Animal("멍멍이");
dog.speak(); // "멍멍이 소리"

speak는 dog 자신의 프로퍼티가 아니다. dog의 프로토타입이 Animal.prototype이라서 찾아지는 거다. 인스턴스를 100개 만들어도 speak 함수는 메모리에 하나만 있다. 메서드를 인스턴스마다 만드는 것과 프로토타입에 두는 것의 차이가 여기서 온다.

2-3. class 는 이걸 감춘 문법일 뿐

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} 소리`; }
}

이건 위 함수 버전과 사실상 같다. speak는 Animal.prototype.speak에 정의된다. typeof Animal을 찍어보면 "function"이 나온다. class는 프로토타입 기반 상속을 숨기지 않고 더 읽기 쉽게 포장한 것뿐이다. extends도 마찬가지로 프로토타입 체인을 연결해주는 문법이다. TypeScript의 interface extends나 type의 &는 모양이 비슷해 보여도 완전히 다르다 — 그건 컴파일하면 사라지는 타입 레벨 표시일 뿐, 지금 보는 것처럼 런타임에 실제 연결이 남지 않는다. 차이는 TypeScript 기초 5 - interface 와 type, 뭘 쓸까에 정리해뒀다.

class Dog extends Animal {
  speak() { return `${super.speak()} (멍멍)`; }
}
new Dog("바둑이").speak(); // "바둑이 소리 (멍멍)"

2-4. JSON.stringify 가 프로토타입 메서드를 무시하는 이유

JSON.stringify는 객체 자신의 열거 가능한 프로퍼티만 훑는다. 프로토타입 체인은 따라가지 않는다. 그래서 class 인스턴스를 JSON.stringify로 찍어보면 메서드는 안 나오고 인스턴스 필드만 나온다.

class Point {
  constructor(x, y) { this.x = x; this.y = y; }
  dist() { return Math.sqrt(this.x ** 2 + this.y ** 2); }
}
JSON.stringify(new Point(1, 2)); // '{"x":1,"y":2}', dist 는 안 나온다

당연해 보이지만, 이 성질을 몰라서 JSON.stringify 결과를 비교해 “값이 바뀌었나”를 판정하는 코드를 짜면 프로토타입에 있는 것들은 애초에 비교 대상에서 빠진다. 값 비교를 JSON.stringify로 대충 하다가 오탐이 난 사례는 프론트 상태 판정 함정 셋에도 나온다 — 거기는 스프레드로 만든 평범한 객체의 키 순서가 문제였지만, “직렬화 규칙대로 비교하는 거지 보이는 대로 비교하는 게 아니다”라는 결은 같다.


3. 마무리

요약

  • 객체는 프로퍼티가 없으면 프로토타입 체인을 따라 올라가며 찾는다
  • new 함수()는 인스턴스의 프로토타입을 그 함수의 prototype에 연결한다
  • class/extends는 프로토타입 체인을 감춘 문법 설탕이다, 메커니즘은 그대로다
  • JSON.stringify는 프로토타입 메서드를 보지 않는다, 값 비교에 쓸 때 주의한다

다음은 await 이다.