자바스크립트, 처음에는 웹페이지에 간단한 효과나 주려고 만든 언어였는데, 어느새 웹 프론트엔드는 물론이고 서버(Node.js)까지 점령한 엄청난 언어가 되었죠! 💪 이렇게 자바스크립트가 강력해진 데에는 여러 독특한 특징들이 숨어있는데요, 그중에서도 객체 지향 프로그래밍을 가능하게 하는 핵심 열쇠가 바로 프로토타입(Prototype)이에요.
오늘은 자바스크립트 객체들이 서로 어떻게 연결되고 기능을 물려받는지, 그 비밀스러운 작동 원리인 프로토타입에 대해 쉽고 재미있게 알아볼게요!
간단히 말해서, 자바스크립트의 모든 객체는 사실 숨겨진 '원형'(Prototype) 객체를 가지고 있다고 생각하면 돼요. 이 원형 객체는 일종의 '부모'나 '설계도' 같은 역할을 하는데요, 자식 객체들에게 공통적인 기능(메서드)이나 속성들을 물려주는 데 사용된답니다.
새로운 객체를 만들 때, 이 원형 객체를 기반으로 만들어지고, 만약 객체 스스로에게 없는 기능이나 속성이 필요하면 자기의 원형 객체에게 물어보러 가는 거죠!
자, 그럼 객체가 자기한테 없는 기능을 원형한테 물어보러 간다고 했죠? 만약 그 원형(부모) 객체에도 그 기능이 없으면 어떡할까요? 포기할까요? 땡! 아닙니다! 😄
그 원형 객체도 자기만의 원형 객체(즉, 할아버지뻘?)를 가지고 있어요. 그럼 자식 객체는 부모한테 물어보고 -> 없으면 할아버지한테 물어보고 -> 없으면 또 그 위로... 이렇게 계속 꼬리에 꼬리를 물고 원형 객체를 찾아 거슬러 올라가면서 원하는 기능이나 속성을 찾아요.
이런 식으로 객체들이 자신의 원형을 통해 연결된 사슬을 바로 프로토타입 체인(Prototype Chain)이라고 불러요. 이게 바로 자바스크립트에서 상속(다른 언어의 클래스 상속과 비슷하지만 좀 달라요!)을 구현하는 핵심 방식이랍니다!
말로만 들으면 살짝 헷갈릴 수 있으니, 간단한 계산기를 만들면서 프로토타입이 어떻게 쓰이는지 직접 눈으로 확인해 볼게요!
// Calculator 라는 이름의 생성자 함수 (객체를 만드는 틀)
function Calculator() {
this.result = 0; // 각 계산기 인스턴스가 가질 고유한 결과값
}
// Calculator의 '원형' 객체(Calculator.prototype)에 공통 기능을 추가해요!
Calculator.prototype.add = function(num) {
this.result += num;
return this.result;
};
Calculator.prototype.subtract = function(num) {
this.result -= num;
return this.result;
};
// 이제 Calculator 틀을 사용해서 실제 계산기 객체를 만들어요!
const calc = new Calculator(); // calc라는 이름의 계산기 탄생!
// calc 객체에는 add, subtract 메서드가 직접 없지만...
// 원형(Calculator.prototype)에 있는 기능을 빌려쓸 수 있어요!
console.log(calc.add(5)); // 결과 5 (0 + 5)
console.log(calc.subtract(2)); // 결과 3 (5 - 2)
const calc2 = new Calculator(); // 또 다른 계산기!
console.log(calc2.add(10)); // 결과 10 (얘는 얘 나름의 result 값을 가져요)
여기서 중요한 점! add와 subtract 함수는 calc 객체 안에 직접 들어있는 게 아니라, calc 객체의 원형인 Calculator.prototype 안에 정의되어 있어요. 하지만 calc 객체는 프로토타입 체인을 통해 마치 자기 것처럼 이 함수들을 사용할 수 있는 거죠! 이렇게 하면 calc, calc2 등 여러 계산기 객체를 만들어도 add, subtract 함수 자체는 메모리에 딱 한 번만 만들어져서 효율적이랍니다. 👍
자바스크립트에 원래부터 들어있는 배열(Array), 문자열(String), 숫자(Number) 같은 내장 객체들도 모두 이 프로토타입 방식을 사용해요. 우리가 배열을 만들고 push, pop, slice, join 같은 편리한 메서드들을 바로 쓸 수 있는 이유가 뭘까요?
네, 맞아요! 바로 Array.prototype이라는 배열의 원형 객체 안에 이 메서드들이 미리 다 정의되어 있기 때문이에요!
const arr = [1, 2, 3];
// arr 객체 자체에는 join 메서드가 없지만,
// 프로토타입 체인을 통해 Array.prototype의 join 메서드를 찾아 사용해요!
console.log(arr.join(', ')); // 결과 "1, 2, 3"
프로토타입은 정말 강력하고 자바스크립트의 핵심이지만, 예전 방식(생성자 함수 + prototype 직접 조작)은 조금 조심해야 할 부분도 있었어요. 모든 자식(인스턴스) 객체들이 하나의 원형(프로토타입) 객체를 공유하다 보니, 만약 누군가 실수로 그 공유하는 원형 객체 자체를 바꿔버리면 모든 자식 객체들이 예기치 않은 영향을 받을 수도 있었거든요. (예를 들어 Calculator.prototype.add = function() { ... } 를 다른 함수로 덮어 써버리면 모든 계산기가 영향을 받겠죠?)
그래서! 요즘 자바스크립트(ES6 이후)에서는 이런 프로토타입 기반의 객체 생성을 좀 더 안전하고 보기 좋게 할 수 있도록 class 문법이 등장했어요.
// ES6 class 문법으로 만든 계산기
class Calculator {
// constructor는 객체가 생성될 때 실행되는 초기 설정 부분이에요.
constructor() {
this.result = 0;
}
// 메서드는 이렇게 바로 정의하면 알아서 prototype으로 들어가요!
add(num) {
this.result += num;
return this.result;
}
subtract(num) {
this.result -= num;
return this.result;
}
}
const calc = new Calculator(); // 사용법은 new 키워드로 동일!
console.log(calc.add(10)); // 결과 10
console.log(calc.subtract(3)); // 결과 7
class 문법이 훨씬 깔끔하고 다른 객체 지향 언어와 비슷해 보이죠? 하지만 중요한 건, class 문법도 사실 내부적으로는 자바스크립트의 프로토타입 방식을 사용하고 있다는 거예요! 우리가 좀 더 편하고 안전하게 프로토타입을 활용할 수 있도록 예쁘게 포장해준 거라고 생각하면 된답니다. 😊
자바스크립트의 프로토타입은 객체들이 어떻게 만들어지고, 어떻게 서로 기능을 공유하고 상속받는지를 이해하는 데 정말 정말 중요한 핵심 개념이에요!
프로토타입과 프로토타입 체인을 이해하면 자바스크립트 코드가 왜 그렇게 동작하는지 더 깊게 알 수 있고, 코드의 재사용성이나 유지보수성도 높일 수 있어요. 우리가 매일 사용하는 내장 객체들의 다양한 메서드들이 어떻게 동작하는지도 이 프로토타입 덕분이고요!
요즘은 class 문법을 더 많이 사용하지만, 그 밑바탕에 깔린 프로토타입의 원리를 알아두면 자바스크립트라는 언어를 더욱 깊이 이해하고 유연하게 활용하는 데 큰 도움이 될 거예요! 😉