이 문서는 자바스크립트 객체지향의 특성, 다른 객체지향 언어와 어떤 점이 다른지 정리하기 위한 문서입니다.
Q. Prototype이 무엇인가요?
A. 객체의 원형, ECMAScript 사양에서는 “prototype”을 “다른 객체에 의해 상속될 프로퍼티들을 제공하는 객체”라고 정의
JS는 객체를 생성할 때, 원본 객체를 기반으로 새로운 객체를 생성하고 이 과정에서 원본 개체의 속성을 상속받습니다.
이를 “프로토타입 체인(prototype chain)”이라고 하며, 이 체인이 계층적으로 연결되어 상속 구조가 형성됩니다.
즉, 한 객체가 자신의 프로토타입 객체를 참조함으로써, 그 프로토타입이 가진 속성과 동작(메서드)을 마치 자신의 것처럼 사용할 수 있게 됩니다.
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로부터 상속)
Object.create
로 연결하는 것만으로 재사용 가능한 패턴 생성프로토타입이 무엇이며, class 기반 객체지향과의 차이를 이해했습니다.
이제 내부로 들어가 조금 더 자세히 알아보겠습니다.
[[ProtoType]]
)모든 객체는 [[ProtoType]]
라는 내부슬롯(Internal Slot)을 가집니다.
Q.
[[ProtoType]]
이 무엇인가요?
A. 슬롯은 해당 객체가 상속할 부모 객체(프로토타입)을 가리키고, 자바스크립트 엔진에 의해 내부적으로 관리됩니다.
엔진에서 내부적으로 관리하기 떄문에 언어차원에서 직접 접근이 불가능하지만, 접근자를 통해 접근이 가능합니다.
→ 접근자를 통해(또는 Object.getPrototypeOf
, Object.setPrototypeOf
) 확인하거나 수정
Q. 갑자기
[[ProtoType]]
에 대한 설명을 왜 하는건가요?
단지 내부속성이기때문에 설명을 진행하는 것은 아닙니다.
[[Prototype]]
이 하는 일이 위에서 설명드렸던 “어떤 일”을 수행하기 위한 수단이기 때문입니다.
[[Prototype]]
슬롯을 통해 객체 B를 참조하고 있으면, A는 B의 모든 프로퍼티 및 메서드를 상속받을 수 있습니다.[[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)
이 과정을 프로토타입 체인이라고 부릅니다
일반 객체와 유사한 속성을 지니지만, 호출에 대한 기능이 추가된 형태입니다.
그리고 함수 객체만 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
의 차이점 요약[[Prototype]]
(내부 슬롯)[[Prototype]]
을 가지고 있으며, 일반 함수의 경우 보통 Function.prototype
__proto__
접근자, Object.getPrototypeOf
, Object.setPrototypeOf
등을 통해 접근 가능prototype
(함수 객체의 프로퍼티)[[Prototype]]
을 지정하기 위해 존재{ constructor: 자신 }
형식의 객체이나, 개발자가 교체 가능prototype
프로퍼티가 없고(혹은 무의미), 오직 함수 객체에만 존재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
함수 객체를 만들고, 그 객체의 prototype
에 greet
를 넣어두는 식JavaScript의 객체지향, Prototype 그리고 우리가 해당 기능들이 어떤식으로 사용하고 있는지 알아보았습니다.
이전에 모듈화에 대한 고민을 했던 때가 있었습니다.
그땐 JS 객체지향 프로그래밍을 완벽하게 이해하지 못했던 때라 class로 작성하는 것과 Prototype으로 작성하는 것의 비교를 정확하게 하지 못했던 것 같습니다.
하지만 오늘의 정리로 인해 이전의 고민들을 해소할 수 있는 시간이 됐습니다.
다음에도 더 알차고 깔끔한 내용으로 찾아뵙겠습니다.
읽어주셔서 감사합니다!