프로토타입(Prototype)과 클래스(Class) - js 구동 메커니즘은 변경되지 않았다.
프로토타입(Prototype)과 클래스(Class)
자바스크립트(JavaScript)를 사용하다 보면 전통적인 객체 지향 언어(Java, C++ 등)와는 무언가 다른 독특한 상속 구조를 마주하게 됩니다. 바로 프로토타입(Prototype)입니다.
ES6에서 클래스(Class) 문법이 도입되면서 다소 익숙한 형태로 코드를 작성할 수 있게 되었지만, 자바스크립트의 본질은 여전히 프로토타입 기반 언어입니다. 이 둘의 핵심 개념과 관계를 명확히 정리했습니다.
1. 자바스크립트의 뿌리: 프로토타입 (Prototype)
자바스크립트의 모든 객체는 자신의 부모 역할을 하는 다른 객체와 연결되어 있습니다. 이 부모 객체를 프로토타입이라고 부릅니다. 객체는 이 프로토타입으로부터 속성(Property)과 메서드(Method)를 상속받아 사용할 수 있습니다.
1) 로토타입 체인 (Prototype Chain)
특정 객체의 속성이나 메서드에 접근하려고 할 때, 해당 객체에 찾는 값이 없다면 엔진은 내부 링크([[Prototype]])를 따라 부모 프로토타입의 속성을 차례대로 검색합니다. 이를 프로토타입 체인이라고 하며, 이 체인의 최상위에는 언제나 Object.prototype이 존재합니다.
2) 프로토타입 기반 상속 예시 코드
ES6 클래스가 없던 시절에는 다음과 같이 생성자 함수와 prototype 속성을 사용하여 상속을 구현했습니다.
// 생성자 함수 정의
function Animal(name){
this.name = name;
}
// 프로토타입을 통한 메서드 정의 (메모리 절약)
Animal.prototype.sayName = function(){
console.log("제 이름은 " + this.name + "입니다.");
};
// 상속받을 하위 생성자 함수 정의
function Dog(name, breed){
Animal.call(this, name); // 부모 생성자 함수 호출 (속성 상속)
this.breed = breed;
}
// 프로토타입 체인 연결 (메서드 상속)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 깨진 생성자 링크 복구
// 하위 함수 전용 메서드 정의
Dog.prototype.bark = function(){
console.log("멍멍!");
};
const myDog = new Dog("바둑이", "진돗개");
myDog.sayName(); // 출력: 제 이름은 바둑이입니다. (부모 메서드 호출)
myDog.bark(); // 출력: 멍멍!*개발자도구(F12)를 눌러서 선언된 function 내부를 확인해보면 Object.prototype을 상속받은 것을 확인가능
2. 모던 자바스크립트의 얼굴: 클래스 (Class)
ES6(2015년) 구조에서 도입된 class 문법은 자바스크립트를 자바나 C++처럼 보이게 만들어 주는 강력한 무기입니다. 기존 프로토타입 기반 상속 코드가 가독성이 떨어지고 난해하다는 피드백을 반영하여 도입되었습니다.
1) 클래스의 핵심 특징
constructor를 통해 객체의 초기 상태를 세팅합니다.extends키워드로 직관적인 상속을 구현합니다.super키워드를 통해 부모 클래스의 생성자나 메서드에 쉽게 접근합니다.
2) 클래스 기반 상속 예시 코드
위에서 작성한 프로토타입 상속 코드를 클래스 문법으로 변환한 모습입니다. 훨씬 직관적이고 깔끔합니다.
class Animal{
constructor(name) {
this.name = name;
}
sayName() {
console.log(`제 이름은 ${this.name}입니다.`);
}
}
class Dog extends Animal{
constructor(name, breed) {
super(name); // 부모 클래스의 constructor 호출
this.breed = breed;
}
bark() {
console.log("멍멍!");
}
}
const myDog = new Dog("바둑이", "진돗개");
myDog.sayName(); // 출력: 제 이름은 바둑이입니다.
myDog.bark(); // 출력: 멍멍!3. 클래스는 단지 '문법적 설탕(Syntactic Sugar)'일까?
많은 개발자가 "클래스는 프로토타입을 보기 좋게 감싸놓은 껍데기(문법적 설탕)일 뿐이다"라고 말합니다. 실제로 클래스로 선언한 타입의 본질을 확인해 보면 이를 알 수 있습니다.
class Person{}
console.log(typeof Person); // 출력: function클래스의 내부 타입은 결국 함수이며, 클래스 내부에 정의한 메서드들은 여전히 Person.prototype에 등록되어 작동합니다.
보충: 단순한 껍데기 그 이상인 이유
완전히 동일하게 취급하기에는 클래스만의 엄격한 제어 기능이 있습니다.
- 클래스는
new키워드 없이 호출하면 즉시 에러(TypeError)가 발생합니다. (일반 생성자 함수는 에러 없이 전역 객체를 오염시킬 위험이 있습니다.) - 클래스 메서드는 자동으로 열거 불가능(
enumerable: false) 상태가 되어for...in루프 등에 노출되지 않습니다. - 클래스 내부 코드는 항상 엄격 모드(
use strict)로 실행됩니다.
*간혹 Map, List 아이템의 내부에 있는 item만 꺼내서 루프를 실행하려고 했는데, 의도치 않게 Object.prototype의 item도 같이 루프가 돌면서 undefinde나 error가 발생하는 이슈를 볼 수 있습니다. 이때, 직접 하나씩 꺼내거나, Object 생성시 상위 item을 상속받지 않게 세팅을 하는 방법이 있습니다.
4. 프로토타입 vs 클래스 요약
결론적으로 성능상의 차이는 거의 없습니다. 내부 엔진은 동일하게 프로토타입 체인을 활용해 메모리를 관리하기 때문입니다. 따라서 모던 자바스크립트 환경에서는 가독성, 유지보수성, 그리고 안정성 측면에서 뛰어난 클래스 문법 사용이 표준으로 자리 잡았습니다.
추가내용 1)
클래스/프로토타입, 타입스크립의 변수타입 등의 개념은 엔진 내에서 새롭게 해석하는 것인가? - (js 구동 메커니즘은 변경되지 않았다.)
기본 베이스가 되는 js의 구동엔진이 메커니즘이 바뀐게 아닙니다. 사용자(개발자) 입장에서 일부 제약조건(룰)을 토대로 작성하도록 했을 뿐입니다. 실질적으로 build된 배포파일을 보면 복잡하지만 바닐라 js와 html로 구성되어있는 부분을 확인 할 수 있습니다. 예시를 들어서 설명하면,
1.)타입스크립트(TS) -> 자바스크립트(JS)의 관계
타입스크립트의 고정된 타입 규칙은 실상 브라우저가 실행하는 엔진 단계(런타임)에는 존재하지도 않습니다.
- 인간이 코딩할 때 (하이레벨):
let age: number = 20;이라 쓰며 타입 규칙을 강제당합니다. 다른 타입을 넣으면 컴파일러가 소리를 지릅니다. - 실제 실행될 때 (로우레벨): 컴파일(빌드)을 거치면
: number같은 타입 규칙은 흔적도 없이 전부 지워지고 그냥let age = 20;이라는 순수 자바스크립트 파일만 남습니다. 즉, 타입스크립트는 로우레벨의 무언가를 바꾼 게 아니라, 개발 단계에서 인간의 실수를 막기 위해 "컴파일 시점의 규칙 검사"라는 가상의 벽만 하나 더 세워둔 것입니다.
2) 클래스(Class) -> 프로토타입(Prototype)의 관계
이것 역시 완벽하게 동일한 구조입니다.
- 인간이 코딩할 때 (하이레벨):
class User { ... }문법을 쓰면서 자바나 C++처럼 정돈된 규칙 아래 코드를 짭니다. - 실제 실행될 때 (로우레벨): 자바스크립트 엔진 내부에서는 클래스라는 개념을 모릅니다. 결국 과거에 쓰던
function User() { ... }생성자 함수와User.prototype이라는 메모리 링크 체인으로 변환되어 실행됩니다. 클래스라는 문법은 단지 프로토타입이라는 로우레벨 메커니즘 위에 "new 키워드 없이 호출하면 에러를 내라", "상속할 때는 extends라는 키워드 규칙을 강제해라"라는 제약 조건(Rule)을 추가해서 포장해 놓은 것에 불과합니다.
3) let/const -> var의 관계
컴퓨터 메모리 관점에서 보면 var를 쓰든 let을 쓰든 메모리에 공간을 할당하고 값을 집어넣는 로우레벨의 물리적 행위는 똑같습니다.
다만 자바스크립트 엔진(V8 등)이 코드를 실행할 때 "let이나 const로 선언된 상자는 중괄호() 밖으로 나가는 코드가 있으면 참조 에러를 던져라", "선언문 실행 전에 접근하면 TDZ 규칙을 적용해 멈춰라"라는 소프트웨어적 규칙을 엔진 내부 코드에 추가한 것뿐입니다.
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.