내장 객체 때도 설명했지만 객체
는 속성(프로퍼티)와 행위(메소드)를 가지는 개념입니다. 그리고 이러한 개념에 명칭을 부여할 수 있어야하기도 합니다.
자바스크립트에서 객체는 객체 리터럴 방식으로 생성했습니다.
let car = {
name: 'sedan',
color: 'yellow',
drive: function() {
console.log("부릉");
}
}
console.log(car.name);
car.drive();
객체 리터럴 방식은 객체를 만들기 위해서는 요소들을 일일히 새로 입력해야하고, 런타임 때 객체 생성이 어렵다는 점을 가지고 있습니다. 그래서 다른 객체지향언어들에는 클래스 개념을 도입해서 쉽게 객체를 생성할 수 있게 해주는데, 자바스크립트에는 클래스라는 개념이 존재하지 않습니다.
대신, 자바스크립트에는 프로토타입(prototype)
이라는 개념이 존재합니다.
프로토타입
이란 어떤 객체의 원본이 되는 객체입니다. 자바스크립트는 이 프로토타입을 기반으로 객체들을 생성할 수 있습니다. 그래서 자바스크립트를 객체지향 언어가 아닌 프로토타입 기반 언어라고 부르기도합니다.
기존 언어를 배우신 분들이라면 클래스와 혼동될 수 있는데, 사실 클래스와 큰 차이점은 없습니다. 다만 프로토타입은 클래스에 비해 좀 더 자유를 주는 개념이라고 생각해도 좋습니다.
그러면 자바스크립트의 프로토타입을 하나 정의해보겠습니다.
const Student = function() {}
그냥 함수 리터럴을 쓴거 아니냐고 할 수도 있는데, 맞습니다. 자바스크립트는 클래스 개념이 없기 때문에 함수에 클래스 역할을 부여할 수 있습니다. 그래서 위와같은 클래스는 다음과 같이 인스턴스화를 할 수 있습니다. 참고로 함수 리터럴로 프로토타입을 정의하지만, 애로우 함수로는 프로토타입을 정의할 수 없습니다.
let std = new Student();
new 연산자를 사용할 때 생성자
에 대해 간략히 언급했었습니다. 생성자
는 객체를 생성(인스턴스화)할 때 객체의 초기화를 하는 특수 메소드입니다.
그러면 프로토타입에 생성자를 만들어보겠습니다.
const Student = function(name, id) {
this.name = name;
this.id = id;
}
let std = new Student('짱구', 001);
this
라는 것이 존재하는데, this
는 생성자로 생성되는 인스턴스, 즉 객체 자기 자신을 가리치는 키워드입니다. 이 this
는 다양한 해석이 존재하므로 나중에 심도있게 다룰 예정이니, 지금은 this
가 인스턴스 객체 자기 자신을 가리킨다고만 알아두세요.
그래서 this.name에 생성시 전달한 인수 name을 넣음으로써 객체의 프로퍼티가 초기화 되는 것 입니다. 이것이 생성자이고, 초기화입니다.
방금 프로퍼티를 선언했으니 이번엔 메소드를 선언해보겠습니다. 메소드도 동일한 방법으로 선언합니다.
const Student = function(name, id) {
this.name = name;
this.id = id;
this.getName = function() {
return this.name;
}
}
let std = new Student('짱구', 001);
메소드를 나중에 동적으로 추가할 수도 있습니다. 방금 전의 예시 코드의 getName 메소드를 동적 추가하는 식으로 바꿔보겠습니다.
const Student = function(name, id) {
this.name = name;
this.id = id;
}
let std = new Student('짱구', 001);
std.getName = function() {
return this.name;
}
인스턴스를 생성하고, 메소드를 추가하는 방식으로 넣으면 동적으로 메소드를 추가할 수 있습니다. 하지만 이 방법엔 문제가 있습니다.
let std = new Student('짱구', 001);
std.getName = function() {
return this.name;
}
let std2 = new Student('흰둥이', 002);
console.log(std2.getName()); //에러!!!
같은 프로토타입으로 만들어낸 인스턴스일지라도 멤버가 완전히 동일하다고는 보장하지 못합니다. 위의 경우 std에는 getName 메소드가 있지만, std2에는 getName 메소드가 선언되지도 않은 상태라는 것 입니다.
그래서 std2에 대해서도 getName을 사용하기 위해서는 따로 멤버를 정의해야합니다. 하지만 이과정에서 쓸데 없는 프로퍼티와 메소드가 복사되어 메모리 공간을 낭비합니다. 이러한 문제점을 해결하고자 prototype 프로퍼티
가 등장하였습니다.
프로토타입명.prototype.멤버명 = ;
prototype 프로퍼티
는 객체에 멤버를 안정적이게 추가하기 위해 생긴 프로퍼티입니다. prototype
을 통해서 멤버를 추가하게 되면, 그 객체의 프로토타입에 멤버가 추가되어, 해당 프로토타입을 기반으로 인스턴스화된 인스턴스들은 모두 추가된 멤버에 접근할 수 있습니다.
말로만 하면 어려우니 다음 예제코드를 보겠습니다. 위에서 작성한 코드를 prototype 프로퍼티
를 통해 멤버를 추가하는 모습입니다.
const Student = function(name, id) {
this.name = name;
this.id = id;
}
Student.prototype.getName = function() {
return this.name;
}
let std = new Student('짱구', 001);
우선 메모리 절감이 가장 큰 장점입니다. 인스턴스화를 시킬 경우 원본 프로토타입에서 참조만 할 뿐 인스턴스에는 복사되지 않기 때문에 메모리 공간을 상당히 아낄 수 있습니다.
두 번째 장점은 인스턴스가 프로토타입을 실시간으로 참조하고있으므로 변화를 빠르게 인지할 수 있다는 점이 있습니다.