Prototype 기반 객체지향

KB LEE·2025년 2월 16일
0

JavaScript 펼쳐보기

목록 보기
4/5
post-thumbnail

목적

이 문서는 자바스크립트 객체지향의 특성, 다른 객체지향 언어와 어떤 점이 다른지 정리하기 위한 문서입니다.


JS는 Prototype기반 객체지향 언어이다.

Prototype?

Q. Prototype이 무엇인가요?
A. 객체의 원형, ECMAScript 사양에서는 “prototype”“다른 객체에 의해 상속될 프로퍼티들을 제공하는 객체”라고 정의

JS는 객체를 생성할 때, 원본 객체를 기반으로 새로운 객체를 생성하고 이 과정에서 원본 개체의 속성을 상속받습니다.
이를 “프로토타입 체인(prototype chain)”이라고 하며, 이 체인이 계층적으로 연결되어 상속 구조가 형성됩니다.
즉, 한 객체가 자신의 프로토타입 객체를 참조함으로써, 그 프로토타입이 가진 속성과 동작(메서드)을 마치 자신의 것처럼 사용할 수 있게 됩니다.

Class 기반 객체지향과의 차이

Q. 그럼 Class기반 객체지향 언어와 무엇이 다른가요?
A. class 기반 객체지향 언어(Java, C++ 등)은 “class”라는 템플릿을 정의하고, 템플릿으로부터 인스턴스 객체를 생성

class는 객체가 가져야 할 속성, 메서드, 상속 구조를 컴파일 시점(또는 초기 단계)에 확정하게 명시하며, 생성된 인스턴스들은 class에 의해 미리 정의된 구조와 동작을 공유


구조적 차이로 인한 특징

클래스 기반 언어에서는 “클래스 정의 → 인스턴스 생성”이 필수적인 반면, 프로토타입 기반 언어(JS)는 “이미 존재하는 객체를 다른 객체의 프로토타입으로 삼아 상속”하는 구조가 핵심

내장 객체(또는 사용자정의 객체)의 프로토타입을 확장

JavaScript는 프로토타입을 통해 이미 생성된 객체나 내장 객체(String, Array 등)의 프로토타입에 새 메서드를 주입할 수 있음

// 1) 내장 객체(String)의 프로토타입에 메서드를 동적으로 추가
String.prototype.printKB = function() {
  console.log(this);
};

"Hi, KB.Lee!".printKB(); 
// 결과: "Hi, KB.Lee!" 이 콘솔에 출력됨

// 2) 사용자 정의 객체의 프로토타입에 프로퍼티(또는 메서드) 추가
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  console.log("Hello, I'm " + this.name);
};

const p = new Person("KB.Lee");
p.greet(); // "Hello, I'm KB.Lee"

// greet를 나중에 삭제할 수도 있음
delete Person.prototype.greet;
p.greet(); // *TypeError: p.greet is not a function*

개별 객체 단위로 상속, 객체 대 객체 단위로 상속/확장이 가능

const grandParent = { propertyA: "A" };
const parent = Object.create(grandParent);
parent.propertyB = "B";

const child = Object.create(parent);

console.log(child.propertyA); // "A" (grandParent로부터 상속)
console.log(child.propertyB); // "B" (parent로부터 상속)

장단점

장점

  1. 문법적 간결함과 유연성
    • 간단한 “공유 메서드를 담을 객체”와 “데이터를 담는 객체”를 Object.create로 연결하는 것만으로 재사용 가능한 패턴 생성
  2. 개별 객체 단위의 상속 구조 설정
    • 프로토타입 기반 언어에서는 “객체 vs 객체” 단위로 상속 관계를 형성
    • 클래스 기반 언어에서는 보통 “클래스 계층 구조”가 먼저 정해지고, 해당 계층을 따르는 인스턴스들이 생성되는 흐름입니다. 프로토타입 언어는 훨씬 유연

단점

  1. 구조적 혼란 및 유지보수 어려움
    • 프로토타입 기반 언어는 런타임에 프로토타입을 바꿀 수도 있고, 객체마다 상속 구조가 제각각일 수 있어, 규모가 커지면 코드 추적이 어려워지고 혼란
  2. 일관성 없는 확장 → 버그 위험
    • 다른 모듈에서 내장 객체나 공용 객체(프로토타입)에 메서드를 추가/삭제해버리면, 프로젝트 전역에 영향
    • 특정 라이브러리가 Array.prototype 등의 기본 객체를 수정하면, 의도치 않은 충돌이나 예기치 않은 동작이 발생
  3. 정적 타입 검사 미흡
    • 자바스크립트는 원래 동적 타이핑 언어이기도 하지만, 프로토타입 기반 특성 자체만 보더라도 “미리 선언된 인터페이스나 클래스” 없이 바로 객체를 조립하므로, 정적 분석이나 컴파일 타임 오류 발견이 어려움
    • 대규모 프로젝트에서는 구조가 자유로운 만큼 버그를 조기에 잡기 어렵고, 팀 협업 시 명확한 계약(Contract)이 부재한 상태로 개발이 진행

객체의 내부

프로토타입이 무엇이며, class 기반 객체지향과의 차이를 이해했습니다.
이제 내부로 들어가 조금 더 자세히 알아보겠습니다.

내부슬롯 ( = [[ProtoType]] )

모든 객체는 [[ProtoType]] 라는 내부슬롯(Internal Slot)을 가집니다.

Q. [[ProtoType]] 이 무엇인가요?
A. 슬롯은 해당 객체가 상속할 부모 객체(프로토타입)을 가리키고, 자바스크립트 엔진에 의해 내부적으로 관리됩니다.

엔진에서 내부적으로 관리하기 떄문에 언어차원에서 직접 접근이 불가능하지만, 접근자를 통해 접근이 가능합니다.
→ 접근자를 통해(또는 Object.getPrototypeOf, Object.setPrototypeOf) 확인하거나 수정

Q. 갑자기 [[ProtoType]] 에 대한 설명을 왜 하는건가요?

단지 내부속성이기때문에 설명을 진행하는 것은 아닙니다.
[[Prototype]] 이 하는 일이 위에서 설명드렸던 “어떤 일”을 수행하기 위한 수단이기 때문입니다.

  1. [[Prototype]] 슬롯을 통해 객체 B를 참조하고 있으면, A는 B의 모든 프로퍼티 및 메서드를 상속받을 수 있습니다.
  2. A에서 어떤 프로퍼티를 찾지 못하면, 자바스크립트 엔진은 A의 [[Prototype]] → B를 확인하고, 그래도 못 찾으면 B의 [[Prototype]]을 재귀적으로 따라 올라갑니다.
const grandParent = { methodGp() { console.log("grandParent"); } };
const parent = Object.create(grandParent); 
parent.methodP = function() { console.log("parent"); };
const child = Object.create(parent); 
child.methodC = function() { console.log("child"); };

child.methodC(); // child
child.methodP(); // parent (child엔 없음 → parent로 올라가 발견)
child.methodGp(); // grandParent (child -> parent -> grandParent)

이 과정을 프로토타입 체인이라고 부릅니다

JavaScript에서는 함수도 객체?

일반 객체와 유사한 속성을 지니지만, 호출에 대한 기능이 추가된 형태입니다.
그리고 함수 객체만 prototype 라는 데이터 프로퍼티를 갖습니다.

일반 함수(function)를 선언하면, 자바스크립트 엔진은 자동으로 그 함수 객체에 prototype 프로퍼티를 생성하고, 그 값으로 { constructor: 함수 } 형태의 객체를 할당하여 갖는 구조입니다.

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

// 출력값 
ƒ Person(name)
    arguments: null
    caller: null
    length: 1
    name: "Person"
    prototype:
        constructor: ƒ Person(name)
        [[Prototype]]: Object
    [[FunctionLocation]]:
    [[Prototype]]: ƒ ()
    [[Scopes]]: Scopes[1]

// property의 기본값: { constructor: Person }
// Object.getOwnPropertyNames(Person.prototype) 하면 ["constructor"]가 나옴
// Person.prototype.constructor === Person

해당 속성은 new로 인스턴스를 생성할 때, 새 객체의 [[Prototype]]을 설정하는 용도로 사용하게 됩니다.
→ “prototype 프로퍼티”는 생성자 함수로서 동작할 때 그 인스턴스들이 참조할 공통 메서드나 속성을 담기 위한 핵심적인 장치

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

Animal.prototype.speak = function() {
  console.log(this.name + " makes a noise.");
};

const dog = new Animal("Dog");
dog.speak(); // "Dog makes a noise."

console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true

new 연산자를 사용하면 내부적으로 아래와 같은 순서를 통해 새로운 인스턴스를 생성합니다.

  • 새 객체를 만든다.
  • 새 객체의 [[Prototype]] = Person.prototype 으로 설정
  • Person 함수를 호출하여, this를 새 객체로 바인딩하고 함수를 실행 (생성자 로직)
  • 새 객체(또는 명시적으로 return된 객체)를 반환

[[Prototype]] vs prototype의 차이점 요약

  1. [[Prototype]] (내부 슬롯)
    • 모든 객체가 갖는 “상속받을 부모 객체”를 참조
    • 함수 객체도 [[Prototype]]을 가지고 있으며, 일반 함수의 경우 보통 Function.prototype
    • __proto__ 접근자, Object.getPrototypeOf, Object.setPrototypeOf 등을 통해 접근 가능
  2. prototype (함수 객체의 프로퍼티)
    • new”로 호출 시, 새로 만들어지는 객체(인스턴스)의 [[Prototype]]을 지정하기 위해 존재
    • 기본값은 { constructor: 자신 } 형식의 객체이나, 개발자가 교체 가능
    • 일반 객체에는 prototype 프로퍼티가 없고(혹은 무의미), 오직 함수 객체에만 존재

ES6의 class?

Q. ES6부터는 class도 있는 것 아닌가요?

부르는 명칭이 다르기 때문에 위 의문증을 가질 수 있습니다.
하지만 ES6에서 추가된 클래스 문법도 내부적으로 생성자 함수 + prototype 방식을 사용

class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log("Hello, I'm " + this.name);
  }
}
const p = new Person("Alice");
p.greet(); // "Hello, I'm Alice"

// 내부적으로 Person은 함수 객체이며, Person.prototype에 greet 메서드가 정의됨
console.log(typeof Person);         // "function"
console.log(Person.prototype.greet); // [Function: greet]

// ------------------- 동일한 결과
function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log("Hello, I'm " + this.name);
};

var p = new Person("Alice");
p.greet(); // "Hello, I'm Alice"
  • 클래스 구문은 문법상 “클래스”처럼 보이지만, 실제 동작은 prototype 체인과 동일
  • 클래스 선언 시 자바스크립트 엔진이 자동으로 Person 함수 객체를 만들고, 그 객체의 prototypegreet를 넣어두는 식

정리 및 후기

JavaScript의 객체지향, Prototype 그리고 우리가 해당 기능들이 어떤식으로 사용하고 있는지 알아보았습니다.

이전에 모듈화에 대한 고민을 했던 때가 있었습니다.
그땐 JS 객체지향 프로그래밍을 완벽하게 이해하지 못했던 때라 class로 작성하는 것과 Prototype으로 작성하는 것의 비교를 정확하게 하지 못했던 것 같습니다.

하지만 오늘의 정리로 인해 이전의 고민들을 해소할 수 있는 시간이 됐습니다.

다음에도 더 알차고 깔끔한 내용으로 찾아뵙겠습니다.

읽어주셔서 감사합니다!


참고자료

MDN - new operator
MDN - Object Prototype

profile
한 발 더 나아가자

0개의 댓글