자바스크립트는 클래스 기반 객체지향 언어(Java, C++)보다 강력한 프로토타입 기반 객체지향 언어이다.
ES6에서 클래스가 도입되었다.
이를 통해 프로토타입의 생성자-프로토타입 패턴 대신, 가독성이 좋은 class 문법을 사용할 수 있게 되었지만, 내부적으로는 여전히 프로토타입 체인을 기반으로 동작하기 때문에 프로토타입에 대해 이해해야 한다.
프로그램을 수많은 객체라는 기본 단위로 나누고 그 객체들 간의 상호작용을 통해 로직을 구성하는 방식
프로토타입을 사용하는 이유는 객체 간의 공통 기능을 재사용해서, 상속을 메모리 사용면에 있어 효율적으로 하기 위해서다.
프로토타입을 이용하지 않고, 같은 getArea함수의 기능을 사용하기위해 circle1과 circle2에게 각각 새로운 getArea함수가 할당되었다.
이런 동일 메서드 중복생성은 불필요한 메모리 낭비를 야기한다.
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
return 3.14 * (this.radius ** 2);
};
}
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea); // false (각각 다른 getArea)
console.log(circle1.getArea());
console.log(circle2.getArea());
하지만 프로토타입을 이용하면, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea함수를 공유 할 수 있다!
function Circle(radius) {
this.radius = radius;
}
// getArea함수를 Circle프로토타입에 추가
Circle.prototype.getArea = function () {
return 3.14 * (this.radius ** 2);
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea); // true (공유된 getArea)
console.log(circle1.getArea());
console.log(circle2.getArea());
먼저, 우리는 수많은 유사한 객체를 유지보수하고 관리하고 싶을 때 앞서 사용했던 생성자 함수(new) 를 사용한다! (이때 new 뒤에 함수는 대문자로 시작해야한다)
function Create(name, sport){
this.name = name;
this.sport = sport;
}
let player1 = new Create('커리', '농구');
let player2 = new Create('르브론', '농구');
그런데 이때 객체에 weight라는 속성을 추가하고 싶다면 2가지 방식이 있다.
- 생성자 Create안에
this.weight코드를 직접 추가해주는 방법(static메서드)- 생성자 Create의 프로토타입에 추가해 주는 방식(
instance메서드)
이때 1번의 경우, 자식들이 weight 속성을 직접 가지는 반면,
2번의 경우는 부모만 weight 속성을 가진다.
따라서 2번의 경우에는 자식들이 부모의 속성인 weight를 마음대로 가져다 쓸 수 있지만 출력해보면 직접 weight를 가지고 있진않다!
✅ 프로토타입으로 상속 사용코드
// 부모
function Create(name, sport){
this.name = name;
this.sport = sport;
}
Create.prototype.weight = '90kg'
// 자식
let player1 = new Create('커리', '농구');
let player2 = new Create('르브론', '농구');
console.log(player1); // Create {name:'커리', sport:'농구'} - weight 없음
console.log(player1.weight); // 90kg
// 프로토타입 출력하기
console.log(player1.__proto__); // { weight: '90kg' }
console.log(Create.prototype); // { weight: '90kg' }
console.log(player1.__proto__ === Create.prototype); // true
이렇게 player1.weight라는 값을 찾을 때 JS는 다음과 같은 동작을 한다.
위처럼 객체의 프로토타입을 따라가며 상속을 구현하는 메커니즘을 프로토타입 체인이라고 한다.
ES5전까진 프로토타입을 통해 객체지향을 구현했지만,
ES6부턴 좀더 JAVA스럽게 객체 지향적으로 표현하기 위해 클래스(Class)가 도입되었다.
다만 생김새만 클래스 구조이지, 엔진 내부적으로는 프로토타입 방식으로 작동된다.
이 둘은 같은 결과를 출력하지만, 문법 생김새만 다르고 내부 로직은 완전히 같은 구조라는 점만 기억하면 된다.
✅ ES5 프로토타입
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayName = function () {
console.log(`Hi ${this.name}`);
};
// 정적 메서드
Person.sayHi = function(){
console.log('안녕! 코린아');
}
let person = new Person("Dongkeun");
person.sayName(); // Hi Dongkeun
person.sayHi(); // ERROR
Person.sayHi(); // 안녕! 코린아
✅ ES6 클래스
class Person {
// 생성자 함수 => constructor
constructor(name){
this.name = name;
}
// 프로토타입 메서드 => 일반객체 메서드선언방식
sayName(){
console.log(`Hi ${this.name}`);
}
// 정적 메서드 => static
static sayHi(){
console.log('안녕! 코린아');
}
}
let person = new Person("Dongkeun");
person.sayName(); // Hi Dongkeun
person.sayHi(); // ERROR
Person.sayHi(); // 안녕! 코린아
class Person {
// 생성자
constructor(name, age){
// 인수로 인스턴스 초기화
this.name = name;
this.age = age;
// 고정값으로 인스턴스 초기화
this.address = 'Seoul';
}
}
let person = new Person('Dongkeun', 27); // 인스턴스 생성
console.log(person); // Person {name: 'DongKeun', age: 27 , address: 'Seoul'}
constructor는 인스턴스를 생성하고 초기화하는 특수한 메서드이다.constructor는 클래스 안에 반드시 한 개만 존재할 수 있다.constructor는 생략할 수 있다. (하지만 인스턴스를 초기화하려면 constructor를 생략하면안된다)constructor는 class에서 필요한 기초 정보를 세팅하는 곳이다. 객체를 new로 생성할 때 가장먼저 자동으로 호출된다.(추가) constructor 생략가능
class Person2 {}
let person2 = new Person(); // 인스턴스 생성
console.log(person2); // Person2 {}
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
sayName(){
console.log(`Hi I'm ${this.name}, ${this.age} years old`);
}
}
let person = new Person("Dongkeun", 27);
person.sayName(); // Hi I'm DongKeun, 27 years old
Person.prototype.sayName으로 만들었던 프로토타입 메서드와 같다.class Person {
constructor(name){
this.name = name;
this.age = 27;
}
static sayHi(){
console.log('안녕! 코린아');
}
static sayError(){
console.log(this.name, this.age);
}
}
let person = new Person("Dongkeun");
person.sayHi(); // ERROR
Person.sayHi(); // 안녕! 코린아
Person.sayError(); // Undefined
static으로 선언한다.static 구문안에서는 constructor에서 선언한 this값(name, age)이 먹히지 않는다!!class Compare {
// static
static staticProp = 'staticProp';
static staticMethod() {
return 'Im a static method.';
}
// instance
instanceProp = 'instanceProp';
instanceMethod() {
return 'Im a instance method.';
}
}
// 상속하면 부모의 static요소들을 사용 가능
console.log(Compare.staticProp); // staticProp
console.log(Compare.staticMethod()); // Im a static method.
console.log(Compare.instanceProp); // Undefined
console.log(Compare.instanceMethod()); // ERROR
// 상속하면 부모의 인스턴스를 사용 가능
const c = new Compare();
console.log(c.staticProp); // Undefined
console.log(c.staticMethod()); // ERROR
console.log(c.instanceProp); // instanceProp
console.log(c.instanceMethod()); // Im a instance method.
클래스 상속 기능을 통해 한 클래스의 기능을 다른 클래스에서 재사용할 수 있다.
class Parent {
constructor (name, age, city) {
this.name = name;
this.city = city;
}
nextYearAge() {
return Number(this.age) + 1;
}
}
class Child extends Parent {
introduce () {
return `저는 ${this.city}에 사는 ${this.name} 입니다.`
}
}
const c = new Child('Lee', 27, 'seoul');
console.log(c.introduce()); // 저는 seoul에 사는 Lee 입니다.
extends 키워드를 통해 Child 클래스가 Parent 클래스를 상속 했다.
상속을 이용하면 부모의 class의 값을 모두 접근하여 사용할 수 있다.
이 관계를 보고 부모 클래스-자식 클래스 관계 혹은 슈퍼 클래스-서브 클래스 관계라고 한다.
class Parent {
constructor (name, age, city) {
this.name = name;
this.age = age;
this.city = city;
}
nextYearAge() {
return Number(this.age) + 1;
}
}
class Child extends Parent {
constructor(name, age, city, futureHope) {
super(name, age, city); // 부모 생성자 가져옴
this.futureHope = futureHope
}
introduce () {
// 부모의 nextYearAge() 메서드 가져옴
return `저는 ${this.city}에 사는 ${this.name} 입니다.
내년엔 ${super.nextYearAge()}살이며,
장래희망은 ${this.futureHope} 입니다.`
}
}
let c = new Child('Lee', 27, 'seoul', '개발자');
console.log(c.introduce()); // 저는 seoul에 사는 Lee입니다. 내년엔 28살이며, 장래희망은 개발자 입니다.
부모 class 값을 상속받고, 추가적으로 자식만의 값을 사용하고싶다면 super 키워드를 사용할 수 있다.
| 특징 | 프로토타입 기반 | class 문법 |
|---|---|---|
| 문법 | 복잡하고 가독성이 낮음 | 직관적이고 객체지향적 |
| 생성자 함수 호출 | new 없이 호출 가능 | new 없이 호출 불가 |
| 상속 | Object.create() 또는 prototype 활용 | extends 사용 가능 |
| 정적 메서드 | 직접 함수 추가 필요 | static 키워드 제공 |