PUBLISHED

생성자 함수와 prototype으로 이해하는 JS class

작성일: 2025.01.31

생성자 함수와 prototype으로 이해하는 JS class

자바스크립트는 프로토타입 기반 언어로, 객체 지향 프로그래밍에서 흔히 사용하는 클래스 상속 모델 대신 프로토타입 체인을 통해 객체 간 상속을 구현한다. 모든 객체는 내부 슬롯에 해당하는 [[Prototype]]을 가지며, 이는 Object.getPrototypeOf() 메서드를 통해 확인할 수 있다. 객체에서 특정 프로퍼티나 메서드를 찾을 수 없을 경우, 자바스크립트 엔진은 이 프로토타입을 따라 상위 객체로 탐색을 이어간다. 이러한 구조가 바로 자바스크립트의 객체 상속과 메서드 공유의 핵심이다.

자바스크립트에서 흥미로운 점은 number, string, boolean과 같은 원시 값조차 객체처럼 메서드를 호출할 수 있다는 점이다. 이는 원시 값에 대해 메서드를 호출하는 순간, 자바스크립트 엔진이 해당 값을 감싸는 래퍼 객체를 일시적으로 생성하기 때문이다. 예를 들어 문자열에서 toUpperCase()를 호출하면 내부적으로 String 객체가 만들어지고, 그 객체의 프로토타입에 정의된 메서드가 실행된다. 메서드 호출이 끝나면 이 래퍼 객체는 즉시 제거되며, 원시 값의 불변성과 경량성은 그대로 유지된다.

ES6 이후 도입된 class 문법은 이러한 프로토타입 기반 구조를 감추고, 보다 전통적인 객체 지향 문법에 익숙한 개발자에게 친숙한 형태를 제공한다. 그러나 클래스는 어디까지나 문법적 설탕에 불과하며, 내부 동작 방식 자체가 바뀐 것은 아니다. 클래스에 정의된 메서드는 실제로 생성자 함수의 prototype 객체에 추가되고, 인스턴스는 프로토타입 체인을 통해 이를 참조한다. 따라서 클래스 문법을 사용하더라도 자바스크립트의 객체 모델을 정확히 이해하기 위해서는 프로토타입 개념에 대한 이해가 여전히 필수적이다.

생성자 함수와 prototype

JavaScript에서 new 키워드를 사용해 함수를 호출하면, 해당 함수는 생성자 함수로 동작한다. 이 과정에서 자바스크립트 엔진은 새로운 객체를 하나 생성하고, 그 객체를 함수 내부의 this에 바인딩한다. 생성자 함수가 명시적으로 다른 객체를 반환하지 않는 한, new와 함께 호출된 함수는 자동으로 이 this를 반환한다. 이때 생성된 객체의 [[Prototype]]은 해당 생성자 함수의 prototype 객체를 가리키게 된다.

이러한 구조 때문에 prototype에 정의된 메서드는 모두 this를 통해 인스턴스의 프로퍼티에 접근할 수 있다. 즉, 생성자 함수에서 this에 할당한 프로퍼티는 인스턴스의 공개(public) 멤버가 되며, prototype에 정의된 메서드는 이 공개 멤버를 사용하는 public 메서드로 동작한다. 아래 예제에서 User.prototype에 정의된 getName, getAge 메서드는 모두 인스턴스의 name, age에 접근할 수 있다.

untitled
TS
function User(name, age) {
  this.name = name;
  this.age = age;
}

User.prototype = {
  constructor: User,
  getName: function () {
    return this.name;
  },
  getAge: function () {
    return this.age;
  },
};

const user = new User("ayden", 30);

console.log(Object.getPrototypeOf(user));
/*
User {
  constructor: [Function: User],
  getName: [Function: getName],
  getAge: [Function: getAge]
}
*/

한편, new로 호출된 생성자 함수는 this만 외부로 반환하므로, 생성자 함수 내부에서 const나 let으로 선언된 변수는 외부에서 직접 접근할 수 없다. 이러한 변수는 자연스럽게 private 멤버 변수가 되며, 이는 동작 원리상 클로저와 동일하다. 중요한 점은 prototype 메서드는 생성자 함수의 스코프에 접근할 수 없기 때문에, private 멤버 변수를 사용하려면 해당 변수를 참조하는 메서드를 반드시 생성자 함수 내부에서 정의해야 한다는 것이다.

아래 예제에서는 name, age가 private 변수로 동작하며, 이 변수에 접근하는 메서드와 보조 함수 역시 생성자 함수 내부에 정의되어 있다. 이 방식으로 private 메서드 또한 구현할 수 있다.

untitled
TS
function User(name, age) {
  const _name = name;
  const _age = age;

  this.getName = function () {
    privateMethod();
    return _name;
  };

  this.getAge = function () {
    return _age;
  };

  function privateMethod() {
    console.log("private method");
  }
}

이처럼 자바스크립트에서는 prototype을 사용한 메서드 공유와, 클로저를 활용한 캡슐화를 조합해 객체의 공개 인터페이스와 내부 구현을 구분할 수 있다. 이는 전통적인 클래스 기반 언어의 접근 제어자와는 다른 방식이지만, 자바스크립트 특유의 유연한 객체 모델을 잘 보여주는 패턴이다.

자바스크립트 클래스의 정체

아래의 예시 코드는 전통적인 객체 지향 언어의 클래스 정의와 매우 유사해 보인다. 하지만 자바스크립트에서 class는 독립적인 개념의 클래스가 아니다. 이 문법은 생성자 함수와 프로토타입 기반 상속을 감싸는 문법적 설탕에 불과하며, 내부적으로는 여전히 함수와 프로토타입을 사용해 동작한다. 즉, 클래스 문법은 개발자가 기존의 프로토타입 패턴을 직접 작성하지 않아도 되도록 추상화한 표현 방식이다.

untitled
JS
class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello ${this.name}`);
  }
}

실제로 class로 선언한 식별자의 타입을 확인해 보면, 그것이 함수라는 사실을 바로 알 수 있다. 이는 클래스 선언이 곧 생성자 함수를 정의하는 것과 동일하다는 의미다. 클래스의 constructor는 생성자 함수 본문에 해당하고, 클래스 내부에 정의된 메서드들은 자동으로 해당 함수의 prototype 객체에 추가된다. 결과적으로 new User()로 생성된 인스턴스는 User.prototype을 자신의 프로토타입으로 참조하게 되며, 메서드 호출 역시 프로토타입 체인을 통해 처리된다.

untitled
JS
typeof User; // "function"

이처럼 클래스 문법은 자바스크립트의 객체 모델을 바꾸지 않는다. 단지 프로토타입 기반 구조를 더 읽기 쉽고 선언적으로 표현할 수 있도록 도와줄 뿐이다.

constructor

클래스에서 constructor는 인스턴스가 생성될 때 가장 먼저 실행되는 특수한 메서드다. new 키워드를 사용해 클래스를 호출하면, 자바스크립트 엔진은 새로운 객체를 생성하고 그 객체를 this에 바인딩한 뒤 constructor를 실행한다. 이 과정은 생성자 함수를 new로 호출했을 때의 동작과 완전히 동일하다.

untitled
JS
class User {
  constructor(name) {
    this.name = name;
  }
}

const user = new User("ayden");
untitled
JS
function User(name) {
  this.name = name;
}

const user = new User("ayden");

위 코드에서 constructor는 인자로 전달받은 name을 인스턴스의 프로퍼티로 초기화한다. 이때 this는 새로 생성된 객체를 가리키며, constructor에서 정의한 모든 this.xxx는 인스턴스의 공개(public) 멤버가 된다. 생성자가 명시적으로 다른 객체를 반환하지 않는 한, new 표현식의 결과는 항상 이 this 객체다.

클래스 문법에서 constructor는 하나만 정의할 수 있다. 여러 개의 생성자를 선언하는 것은 허용되지 않으며, 이는 생성자 함수 패턴과 동일한 제약이다. 만약 생성자를 정의하지 않으면 자바스크립트 엔진이 기본 생성자를 자동으로 추가한다.

untitled
JS
class User {
  // constructor가 없어도 문제 없음
}

이 경우 내부적으로는 다음과 같은 생성자가 존재하는 것과 같다.

untitled
JS
class User {
  constructor() {}
}

생성자는 주로 인스턴스의 초기 상태를 설정하는 역할을 맡지만, 단순히 프로퍼티를 할당하는 용도에만 국한되지는 않는다. 앞서 살펴본 것처럼, 생성자 내부에서 클로저를 활용하면 private 멤버 변수를 정의할 수도 있고, 인스턴스마다 고유한 상태나 동작을 부여할 수도 있다. 이러한 특성 역시 클래스 문법이 생성자 함수 위에 얹힌 문법이라는 점을 잘 보여준다.

untitled
JS
class User {
  constructor(name) {
    const _name = name; // p클로저를 활용하는 rivate 변수

    this.getName = function () {
      return _name;
    };

    this.setName = function (newName) {
      _name = newName;
    };
  }
}

const user = new User("ayden");

user.getName(); // "ayden"
user._name;     // undefined

인스턴스 메서드

클래스에서 메서드를 정의하면, 해당 메서드는 인스턴스에 직접 생성되지 않고 생성자 함수의 prototype 객체에 등록된다. 이는 생성자 함수 패턴에서 User.prototype.getName = function () {} 형태로 메서드를 정의하던 방식과 동일하다. 클래스 문법은 이 과정을 감추고 있을 뿐, 메서드가 공유된다는 사실 자체는 변하지 않는다.

untitled
JS
class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello ${this.name}`);
  }
}

const user1 = new User("ayden");
const user2 = new User("minsu");
untitled
JS
function User(name) {
  this.name = name;
}

User.prototype.greet = function () {
  console.log(`Hello ${this.name}`);
};

const user1 = new User("ayden");
const user2 = new User("minsu");

위 코드에서 greet 메서드는 user1이나 user2 각각의 객체에 존재하지 않는다. 대신 두 인스턴스는 모두 User.prototype을 자신의 프로토타입으로 참조하고 있으며, greet는 그 프로토타입 객체에 단 하나만 존재한다. 메서드 호출 시 자바스크립트 엔진은 먼저 인스턴스 자체에서 프로퍼티를 찾고, 없을 경우 프로토타입 체인을 따라 User.prototype에서 메서드를 발견해 실행한다.

untitled
JS
user1.greet === user2.greet; // true

이 결과는 인스턴스 메서드가 공유되고 있다는 사실을 명확히 보여준다. 이러한 구조 덕분에 클래스 기반 코드에서도 메모리 사용량을 효율적으로 관리할 수 있으며, 다수의 인스턴스를 생성하더라도 메서드는 한 번만 정의된다.

또한 클래스 문법으로 정의된 메서드는 기본적으로 enumerable: false 속성을 가진다. 이는 for...in이나 Object.keys 같은 순회 과정에서 메서드가 노출되지 않도록 하기 위한 설계로, 객체의 상태 데이터와 동작을 자연스럽게 구분하는 데 도움을 준다. 이 역시 직접 프로토타입에 메서드를 정의하던 패턴을 보다 안전하게 다듬은 결과다.

프로퍼티

자바스크립트에서 필드, 즉 프로퍼티는 인스턴스가 가지는 상태(state)를 표현하는 값이다. 클래스 문법을 사용하든 생성자 함수를 사용하든, 필드는 항상 인스턴스 객체에 직접 생성된다. 이는 자바스크립트 객체 모델의 핵심적인 특성으로, 필드는 프로토타입이 아닌 개별 객체에 속한다. 따라서 필드는 인스턴스마다 서로 다른 값을 가질 수 있으며, 메서드처럼 공유되지 않는다.

가장 전통적인 필드 정의 방식은 생성자 내부에서 this에 값을 할당하는 것이다. 이 방식은 클래스 문법 이전부터 사용되던 패턴이며, 클래스 문법에서도 그대로 유지된다.

untitled
JS
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const user1 = new User("ayden", 30);
const user2 = new User("minsu", 25);
untitled
JS
function User(name, age) {
  this.name = name;
  this.age = age;
}

const user1 = new User("ayden", 30);
const user2 = new User("minsu", 25);

위 코드에서 name과 age는 모두 인스턴스 자체에 존재하는 프로퍼티다. 따라서 각 인스턴스는 서로 완전히 독립적인 상태를 가진다. 이는 프로토타입에 정의된 메서드가 모든 인스턴스에서 공유되는 것과 명확히 대비된다.

ES2022 클래스 필드

ES2022에서는 생성자 밖에서 필드를 선언할 수 있는 클래스 필드 문법이 표준으로 확정되었다. 이 문법을 사용하면 클래스의 구조를 한눈에 파악할 수 있으며, 어떤 필드가 해당 인스턴스의 상태를 구성하는지 명시적으로 드러낼 수 있다.

untitled
JS
class User {
  name;
  age;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

또한 필드 선언과 동시에 초기값을 지정할 수도 있다.

untitled
JS
class User {
  role = "user";

  constructor(name) {
    this.name = name;
  }
}

이 문법은 겉보기에는 클래스에 새로운 기능이 추가된 것처럼 보이지만, 실제 동작은 생성자 내부에서 this.role = "user"를 실행하는 것과 동일하다. 즉, 클래스 필드 문법 역시 인스턴스 생성 시점에 프로퍼티를 초기화하는 문법적 표현일 뿐, 객체 모델 자체를 변경하지는 않는다.

화살표 함수 필드

클래스에서 화살표 함수를 사용해 정의한 멤버는 메서드가 아니라, 인스턴스 필드에 할당된 함수다. 이 패턴은 ES2022 클래스 필드 문법과 화살표 함수의 특성이 결합된 형태로, 프로토타입에 메서드를 정의하는 방식과 구조적으로 다르다. 가장 큰 차이는 함수가 인스턴스마다 생성되며, this가 렉시컬하게 고정된다는 점이다.

untitled
JS
function User(name) {
  this.name = name;

  this.greet = () => {
    console.log(`Hello ${this.name}`);
  };
}

const user1 = new User("ayden");
const user2 = new User("minsu");
untitled
JS
class User {
  constructor(name) {
    this.name = name;
  }

  greet = () => {
    console.log(`Hello ${this.name}`);
  };
}

const user1 = new User("ayden");
const user2 = new User("minsu");

두 코드 모두에서 greet는 User.prototype에 존재하지 않는다. 대신 각 인스턴스가 자신의 greet 함수를 직접 소유한다. 따라서 다음 비교 결과는 false가 된다.

untitled
JS
user1.greet === user2.greet; // false

이는 앞서 살펴본 인스턴스 메서드와의 결정적인 차이다. 프로토타입 메서드는 하나의 함수를 여러 인스턴스가 공유하지만, 화살표 함수 필드는 인스턴스마다 새로운 함수가 생성된다.

화살표 함수 필드는 this가 항상 인스턴스를 가리켜야 하는 경우에 적합하다. 대표적으로 이벤트 핸들러, 콜백으로 자주 전달되는 함수, 혹은 this 바인딩 오류를 원천적으로 차단하고 싶은 경우가 이에 해당한다. 반면 다수의 인스턴스를 생성하는 구조에서 공통 동작을 정의해야 한다면, 메모리 효율 측면에서 프로토타입 메서드가 더 합리적이다.

결과적으로 화살표 함수 필드는 메서드의 대체재가 아니라, 명확한 의도를 가진 또 하나의 선택지다. 클래스 문법 안에 존재한다고 해서 모두 같은 성격의 멤버는 아니며, 프로토타입 메서드와 인스턴스 필드를 구분해 사용하는 것이 중요하다.

상속

자바스크립트에서 클래스 상속은 기존 클래스의 기능을 재사용하고 확장하기 위한 문법이다. extends 키워드를 사용하면 자식 클래스는 부모 클래스의 프로토타입을 상속받는다. 이때 상속의 본질은 여전히 프로토타입 체인이며, 클래스 문법은 이를 선언적으로 표현할 뿐이다.

untitled
JS
class Animal {
  speak() {
    console.log("sound");
  }
}

class Dog extends Animal {
  bark() {
    console.log("woof");
  }
}

const dog = new Dog();
dog.speak(); // "sound"
dog.bark();  // "woof"
untitled
JS
function Animal() {}

Animal.prototype.speak = function () {
  console.log("sound");
};

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log("woof");
};

const dog = new Dog();
dog.speak(); // "sound"
dog.bark();  // "woof"

위 두 코드에서 핵심은 동일하다. Dog 인스턴스의 [[Prototype]] 체인 상에 Animal.prototype이 존재하며, 이로 인해 부모의 메서드에 접근할 수 있다. 클래스 문법은 Object.create와 constructor 재설정 과정을 자동으로 처리해 준다.

단일 상속만 가능한 이유

자바스크립트의 클래스 상속이 단일 상속만 허용하는 이유는 문법적인 제약 때문이 아니라, 프로토타입 체인의 구조 자체에 있다. 자바스크립트 객체는 오직 하나의 [[Prototype]]만 가질 수 있으며, 이 단일 참조 구조가 상속 모델의 한계를 결정한다. 클래스 문법은 이 구조를 더 선언적으로 표현한 것일 뿐, 프로토타입 체인의 본질을 확장하지는 않는다.

객체에서 프로퍼티에 접근할 때 자바스크립트 엔진은 먼저 객체 자신을 확인하고, 존재하지 않을 경우 [[Prototype]]이 가리키는 객체로 탐색을 이어간다. 이 탐색 과정은 항상 위로 한 단계씩 진행되는 선형 구조이며, 분기나 병합을 허용하지 않는다. 즉, 하나의 객체가 동시에 여러 프로토타입을 참조하는 구조는 언어 차원에서 존재하지 않는다.

untitled
JS
const obj = {};
Object.getPrototypeOf(obj); // 하나뿐이다

클래스에서 extends를 사용하면, 자식 클래스의 prototype은 부모 클래스의 prototype을 참조하도록 설정된다. 이로 인해 형성되는 구조 역시 단일한 프로토타입 체인이다.

untitled
JAVA
class A {}
class B extends A {}

위 코드는 내부적으로 다음과 같은 관계를 만든다.

untitled
JS
B.prototype.__proto__ === A.prototype;

B.prototype은 오직 하나의 객체, 즉 A.prototype만을 가리킨다. 만약 자바스크립트가 다중 상속을 허용하려면, B.prototype이 여러 개의 프로토타입을 동시에 참조할 수 있어야 한다. 하지만 [[Prototype]] 슬롯은 구조적으로 단 하나만 존재한다.

가령 다음과 같은 상속이 가능하다고 가정해 보자. 이 경우 C.prototype은 어떤 프로토타입을 먼저 탐색해야 할까? A.prototype인가, B.prototype인가? 두 부모에 동일한 이름의 메서드가 있다면 어떤 것을 호출해야 할지도 정의할 수 없다. 이러한 모호성은 프로토타입 체인의 선형 탐색 규칙과 충돌한다.

untitled
JAVA
class C extends A, B {}

자바스크립트의 객체 탐색 규칙은 의도적으로 단순하다. 프로퍼티를 찾기 위해 위로 한 단계씩, 하나의 경로만 따라간다. 이 단순함은 성능과 예측 가능성을 보장하기 위한 설계 선택이다. 그 결과, 다중 상속은 언어 차원에서 배제되었다.

자바스크립트는 다중 상속 대신 합성(composition) 을 권장한다. 여러 기능을 조합하고 싶다면, 상속이 아니라 위임이나 믹스인 패턴을 사용하는 방식이다. 이 방식은 프로토타입 체인을 복잡하게 만들지 않으면서도, 여러 객체의 동작을 유연하게 조합할 수 있게 해준다. 상속 구조를 깊게 만드는 대신, 필요한 기능을 선택적으로 결합하는 접근 방식이다.

untitled
JS
const canFly = {
  fly() {}
};

const canSwim = {
  swim() {}
};

class Duck {}
Object.assign(Duck.prototype, canFly, canSwim);

결국 자바스크립트에서 단일 상속만 가능한 이유는 언어의 한계라기보다, 프로토타입 체인을 단순하고 예측 가능하게 유지하기 위한 구조적 선택에 가깝다. 이 특성을 이해하면, 클래스 상속을 언제 사용하고 언제 합성을 선택해야 하는지도 자연스럽게 판단할 수 있다.

super

super는 상속 관계에서 부모 클래스에 접근하기 위한 키워드다. 자식 클래스는 부모 클래스의 생성자나 메서드를 직접 호출할 수 없으며, 이 역할을 수행하는 문법적 장치가 바로 super다. 클래스 문법에서 super는 새로운 개념처럼 보이지만, 실제로는 생성자 함수 패턴에서 부모 생성자나 부모 프로토타입 메서드를 명시적으로 호출하던 방식을 정리한 표현에 가깝다.

생성자에서의 super

extends를 사용해 클래스를 상속받고, 자식 클래스에서 constructor를 정의했다면 super() 호출은 필수다. super()는 부모 클래스의 생성자를 실행하며, 이 호출이 완료되어야만 this를 사용할 수 있다. 이는 자바스크립트 엔진이 인스턴스를 초기화하는 순서를 엄격하게 보장하기 위한 제약이다.

untitled
JS
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
}

const dog = new Dog("buddy");
untitled
JS
function Animal(name) {
  this.name = name;
}

function Dog(name) {
  // 부모 생성자를 현재 Dog 인스턴스 문맥에서 호출
  // → class 문법의 super(name)과 동일한 역할
  Animal.call(this, name);
}

const dog = new Dog("buddy");

위 코드에서 super(name)은 부모 클래스인 Animal의 생성자를 호출한다. 이 호출을 통해 this.name이 초기화되며, 그 이후에야 자식 클래스에서 this를 안전하게 사용할 수 있다.

super()를 호출하지 않고 this에 접근하면 즉시 에러가 발생한다.

untitled
JS
class Dog extends Animal {
  constructor(name) {
    this.name = name; // ReferenceError
  }
}

메서드에서의 super

super는 생성자뿐 아니라 메서드 내부에서도 사용할 수 있다. 이 경우 super는 부모 클래스의 프로토타입 메서드를 가리킨다. 자식 클래스는 이를 통해 부모의 동작을 재사용하거나 확장할 수 있다.

untitled
JS
class Animal {
  speak() {
    console.log("sound");
  }
}

class Dog extends Animal {
  speak() {
    super.speak();
    console.log("woof");
  }
}

const dog = new Dog();
dog.speak();

이때 super.speak()는 Animal.prototype.speak를 현재 인스턴스의 문맥에서 호출한다. 즉, this는 여전히 dog 인스턴스를 가리킨다.

생성자 함수 문법으로 구현된 아래의 방식 역시 부모 프로토타입의 메서드를 직접 찾아 호출하는 구조다. 클래스 문법은 이 패턴을 더 간결하고 안전하게 표현할 수 있도록 추상화한 것이다.

untitled
JS
Dog.prototype.speak = function () {
  Animal.prototype.speak.call(this);
  console.log("woof");
};

Static

정적 메서드는 인스턴스가 아닌 클래스 자체에 속한 메서드다. 인스턴스를 생성하지 않고도 호출할 수 있으며, 개별 객체의 상태와는 무관한 로직을 표현할 때 사용된다. 자바스크립트에서 클래스는 결국 생성자 함수이기 때문에, 정적 메서드는 생성자 함수 객체에 직접 정의된 프로퍼티라고 이해하는 것이 가장 정확하다.

정적 메서드는 프로토타입 체인에 포함되지 않는다. 따라서 인스턴스를 통해 접근할 수 없고, 클래스(혹은 생성자 함수) 이름을 통해서만 호출할 수 있다. 이 구조는 인스턴스의 책임과 클래스의 책임을 명확히 분리하기 위한 설계다.

untitled
JS
class User {
  constructor(name) {
    this.name = name;
  }

  static createGuest() {
    return new User("guest");
  }
}

const guest = User.createGuest();
guest.createGuest; // undefined
untitled
JS
function User(name) {
  this.name = name;
}

User.createGuest = function () {
  return new User("guest");
};

const guest = User.createGuest();
guest.createGuest; // undefined

위 코드에서 createGuest는 인스턴스나 prototype에 존재하지 않고, 오직 생성자 함수 객체에만 존재한다. 클래스 문법의 static 키워드는 이 패턴을 문법적으로 드러내기 위한 장치일 뿐, 새로운 객체 모델을 도입하지는 않는다.

정적 메서드는 주로 팩토리 함수, 유틸리티 로직, 혹은 클래스와 직접적으로 연관된 메타 기능을 정의하는 데 사용된다. 인스턴스의 상태를 다루지 않는다는 점에서, 인스턴스 메서드와는 명확히 구분되어야 한다.

또한 정적 메서드는 상속된다. 이는 인스턴스 상속이 아니라 생성자 함수 간의 프로토타입 연결을 통해 이루어진다. 자식 클래스의 생성자 함수는 부모 클래스의 생성자 함수를 프로토타입으로 참조하며, 이 경로를 통해 정적 메서드에 접근할 수 있다.

결국 static은 클래스 문법에서 “이 메서드는 인스턴스의 것이 아니다”라는 의도를 명확히 표현하는 수단이다. 이를 생성자 함수 관점에서 함께 이해하면, 정적 메서드의 위치와 역할이 자연스럽게 정리된다.

마무리

자바스크립트의 클래스 문법은 전통적인 객체 지향 언어의 클래스와 유사한 형태를 제공하지만, 그 본질은 어디까지나 프로토타입 기반 객체 모델 위에 얹힌 표현이다. class, constructor, extends, super, static 같은 키워드는 새로운 동작 방식을 도입하기 위한 것이 아니라, 기존의 생성자 함수와 프로토타입 패턴을 더 읽기 쉽고 안전하게 사용할 수 있도록 정리한 문법적 장치에 가깝다.

이 글에서 살펴본 생성자, 인스턴스 메서드, 필드, 화살표 함수 필드, 상속과 super, 그리고 정적 메서드는 모두 서로 다른 개념처럼 보이지만, 결국 하나의 공통된 구조로 환원된다. 인스턴스는 생성자 함수에 의해 만들어지고, 메서드는 프로토타입 체인을 통해 공유되며, 필드는 인스턴스에 직접 존재한다. 클래스 문법은 이 구조를 감추지 않고, 오히려 더 명확하게 드러내는 역할을 한다.

특히 클래스 문법을 사용할 때도 프로토타입 체인을 이해하는 것이 중요한 이유는, 문제의 원인과 해법이 항상 그 구조 안에 있기 때문이다. this 바인딩 문제, 상속 관계에서의 메서드 해석, 메모리 사용 방식, 그리고 단일 상속이라는 제약 역시 모두 프로토타입 모델에서 자연스럽게 설명된다.

결국 자바스크립트에서 클래스를 잘 사용한다는 것은, 클래스를 전통적인 객체 지향의 출발점으로 받아들이는 것이 아니라, 프로토타입 기반 언어가 제공하는 여러 표현 중 하나로 이해하고 선택하는 것에 가깝다. 이 관점을 갖추면 클래스 문법은 추상적인 개념이 아니라, 동작을 예측할 수 있는 도구로 자리 잡게 된다.