자바스크립트는 클래스(class) 기반 언어가 아닌 프로토타입(prototype) 기반 언어이다.
객체지향 프로그래밍을 할 때 클래스에서 객체를 찍어내는 방식(Java, C++, Python 등)이 아니라, 기존 객체(프로토타입)를 복사하고 수정해서 새로운 객체를 만드는 방식을 사용한다.
프로토 타입(Prototype)이란?
자바스크립트에서 객체는 다른 객체의 값을 상속 받을 수 있으며, 그 기반이 되는 객체를 '프로토타입'이라고 한다.
프로토타입은 말 그대로 원본, 원형, 시제품의 의미이며 새로운 객체의 거푸집(몰드, 형틀) 역할을 한다.
상속?
- 원본이 되는 객체(프로토타입)의 속성과 메서드과 메서드를 '참조'한다는 의미다. 원본 데이터는 그대로 존재하고 메모리 주소를 참조하는 방식에 가깝다.
- 즉 원본 데이터를 복사해서 붙여넣는 방식이 아닌 원본 데이터에 접근하고 사용할 수 있는 것이다. 다만 어디까지나 참조이기 때문에 원본 데이터를 수정하거나 삭제하는 등 변경하는 것은 불가능하다.
폐쇄형 구조에도 접근이 가능한가?
- 자바 스크립트에는 1. 클로저, 2. 즉시 실행 함수 표현식(IIFE), 3. 모듈, 4. 심볼 같은 폐쇄형 구조의 코드들이 있긴하지만 해당 코드가 외부와 단절됐다는 것이지(외부로부터의 직접적인 접근을 제한하고 내부 데이터를 보호한다)
- 해당 코드에게서 상속을 받은 새로운 객체를 만들지 못한다는 것은 아니다.
- 기본적으로 모든 객체는 다른 모든 객체에게서 상속 받을 수 있다.
프로토타입 체인(Prototype Chain)
- 객체의 프로토타입이 또 다른 프로토타입을 가리키는 방식으로 연결되어 있는 것을 프로토타입 체인이라고 한다.
- 말 그대로 B가 A를 상속 받고 C가 B를 상속 받아서 A-B-C 식으로 연결되어 있다는 것이다.
- 여기서 C는 자신과 연결된 B에서 속성이나 메서드를 찾지 못하면 A까지 거슬러 올라가면서 찾게된다.
- 즉 자신이 상속받은 프로토타입이 있다면, 해당 프로토타입의 상위 프로토타입까지 참조를 통한 접근을 허용하는 것이라고 보면 된다.
- 때문에 객체에서 속성이나 메서드를 찾을 때, 해당 객체에서 찾지 못하면 프로토타입 체인을 따라 올라가면서 찾는다.
프로토타입 비유 및 예시
- 가족 관계 비유: 프로토타입을 가족 관계에 비유하면, 부모로부터 유전자를 물려받는 자식과 같습니다. 자식은 부모의 유전자를 상속받아 특정 특성을 가지게 되며, 부모가 가진 특성이 자식에게도 전달되는 것과 같습니다.
프로토 타입 연결 방식
- 객체 연결
- 자바스크립트의 모든 객체는 [[Prototype]]이라는 숨겨진 내부 슬롯을 가지고 있다.
- 이 슬롯은 다른 객체 또는 null을 가리키며, 이 연결을 통해 객체 간에 상속 관계를 형성한다.
- 해당 슬롯은 1개뿐이라 1개의 객체가 여러개의 프로토타입을 한 번에 상속 받는 것은 불가능하다.
- 프로토타입 체인
- 하지만 프로토타입 객체 또한 [[Prototype]]을 통해 다른 프로토타입 객체에게 상속 받을 수 있다.
- 객체에서 특정 속성이나 메서드를 찾을 때, 해당 객체에 없으면 [[Prototype]]을 따라 연결된 상위 프로토타입 객체에서 찾는다.
- 이러한 방식으로 하위 객체는 상위 프로토타입 객체의 속성과 메서드를 상속받아 사용할 수 있다.
- 이처럼 객체는 한 번에 1개의 프로토타입을 가지지 못하더라도 실제로는 여러개의 프로토타입으로 부터 상속을 받는 효과를 낼 수 있다.
- 이러한 프로토타입 객체들의 연결을 프로토타입 체인이라고 한다.
예시
자바스크립트에서 프로토타입 연결 방법은 크게 두 가지로 나눌 수 있다.
객체 리터럴을 사용하는 방법과 생성자 함수를 사용하는 방법이다.
1. 객체 리터럴을 사용하는 방법
Object.create() 메서드: 이 메서드는 지정된 프로토타입 객체를 사용하여 새 객체를 생성한다.
const parent = {
sayHello() {
console.log("안녕하세요!");
},
};
const child = Object.create(parent);
child.name = "아이";
child.sayHello(); // "안녕하세요!"
console.log(child.__proto__ === parent); // true
2. 생성자 함수를 사용하는 방법
생성자 함수의 prototype 프로퍼티: 생성자 함수로 생성된 모든 객체는 생성자 함수의 prototype 프로퍼티를 프로토타입으로 갖는다.
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function () {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// Child의 프로토타입을 Parent의 프로토타입으로 설정
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // constructor 프로퍼티를 다시 Child로 설정
const child1 = new Child("아이", 10);
child1.sayHello(); // "안녕하세요, 제 이름은 아이입니다."
console.log(child1.__proto__.__proto__ === Parent.prototype); // true
3. __proto__ 접근자 프로퍼티를 사용하는 방법
const parent = {
sayHello() {
console.log("안녕하세요!");
},
};
const child = {
name: "아이",
};
child.__proto__ = parent;
child.sayHello(); // "안녕하세요!"
console.log(child.__proto__ === parent); // true
- __proto__ 접근자 프로퍼티: 이 프로퍼티를 사용하여 객체의 프로토타입을 직접 설정할 수 있다.
- 하지만 Object.getPrototypeOf() 및 Object.setPrototypeOf() 메서드를 사용하는 것이 더 안전하고 권장된다.
4. Object.setPrototypeOf() 를 사용하는 방법
// 부모 객체
const parent1 = {
sayHello() {
console.log('부모1이 말합니다. 안녕하세요!');
},
};
const parent2 = {
sayHello() {
console.log('부모2가 말합니다. 안녕하세요!');
},
sayGoodbye() {
console.log('부모2가 말합니다. 안녕히 가세요!');
},
};
// 자식 객체
const child = {
name: '아이',
};
// 초기 프로토타입 설정: parent1
Object.setPrototypeOf(child, parent1);
// child 객체에서 parent1의 메서드 사용
child.sayHello(); // "부모1이 말합니다. 안녕하세요!"
// 현재 프로토타입 확인
console.log(Object.getPrototypeOf(child) === parent1); // true
// 프로토타입 변경: parent2
Object.setPrototypeOf(child, parent2);
// child 객체에서 parent2의 메서드 사용
child.sayHello(); // "부모2가 말합니다. 안녕하세요!"
child.sayGoodbye(); // "부모2가 말합니다. 안녕히 가세요!"
// 변경된 프로토타입 확인
console.log(Object.getPrototypeOf(child) === parent2); // true
- parent1과 parent2 객체 생성: 두 개의 부모 객체를 생성하고 각각 다른 sayHello() 메서드를 정의한다.
- parent2는 추가적으로 sayGoodbye() 메서드도 가지고 있다.
- child 객체 생성: 자식 객체를 생성하고 name 속성을 정의한다.
- 초기 프로토타입 설정 (Object.setPrototypeOf()): Object.setPrototypeOf()를 사용하여 child 객체의 프로토타입을 parent1 객체로 설정한다.
- 초기 프로토타입 메서드 사용: child 객체에서 parent1의 sayHello() 메서드를 호출하고, Object.getPrototypeOf()를 사용하여 child의 현재 프로토타입을 확인한다.
- 프로토타입 변경 (Object.setPrototypeOf()): Object.setPrototypeOf()를 다시 사용하여 child 객체의 프로토타입을 parent2 객체로 변경한다.
- 변경된 프로토타입 메서드 사용: child 객체에서 parent2의 sayHello() 및 sayGoodbye() 메서드를 호출하고, Object.getPrototypeOf()를 사용하여 child의 변경된 프로토타입을 확인한다.
'JavaScript > 개념 조각' 카테고리의 다른 글
✨ 화살표 함수와 일반 함수의 변환 (0) | 2025.03.29 |
---|---|
Set 생성자 및 add 메서드 동작에 대한 의문사항 (0) | 2025.03.28 |
생성자 함수 (0) | 2025.03.25 |
IIFE(Immediately Invoked Function Expression) (0) | 2025.03.24 |
변수, 함수 선언식, 함수 표현식 호이스팅 여부 (0) | 2025.03.24 |