JavaScript의 프로토타입 (Prototype)은 매우 중요한 개념이다. JavaScript는 프로토타입 기반 언어라고 불린다. 이는 객체 지향 언어의 일반적인 클래스 기반 상속과는 조금 다른 메커니즘을 사용한다.
JavaScript에서 모든 객체는 다른 객체를 참조하는 내부 링크, 즉 프로토타입을 가지고 있습니다. 이 프로토타입 객체로부터 객체는 메서드와 속성을 상속받을 수 있다.
프로토타입 연쇄 (Prototype Chain): 객체의 특정 속성 또는 메서드에 접근하려 할 때, 그 객체에 해당 속성/메서드가 없으면 자바스크립트는 객체의 프로토타입에서 그 속성/메서드를 찾는다. 프로토타입에도 해당 속성/메서드가 없다면 프로토타입의 프로토타입을 검사한다. 이 과정은 프로토타입 연쇄의 끝에 도달할 때까지, 즉 null 프로토타입에 도달할 때까지 반복된다.
공유 속성: 여러 객체가 같은 프로토타입을 참조하면 해당 프로토타입의 속성과 메서드를 공유한다. 따라서 프로토타입에 정의된 메서드나 속성은 모든 객체에서 사용할 수 있다.
확장성: 프로토타입을 사용하면 기존 객체를 확장하거나 변경하는 것이 쉽다. 예를 들어, 모든 배열 객체에 새로운 메서드를 추가하려면 Array.prototype에 메서드를 추가하면 된다.
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const john = new Person('John');
john.sayHello(); // Hello, my name is John
const jane = new Person('Jane');
jane.sayHello(); // Hello, my name is Jane
여기서 sayHello 메서드는 Person.prototype에 추가되었으므로, Person로부터 생성된 모든 객체 (john 및 jane 포함)는 이 메서드에 접근할 수 있다.
요약하면, JavaScript의 프로토타입은 객체 간에 속성과 메서드를 효율적으로 공유하고, 동적으로 객체를 확장하거나 수정하는 데 유용한 메커니즘을 제공한다.
let user = {
name: "John",
age: 45
}
console.log(user.name);
console.log(user.hasOwnProperty("email"));
// Expected Output
// John
// false
여기서 hasOwnProperty는 어디에서 왔을까?
(현재 user 객체 변수에는 두 개의 속성(name, age)만 존재한다.)
hasOwnProperty 메서드는 Object.prototype에서 상속받은 메서드이다.
JavaScript에서 거의 모든 객체는 Object.prototype을 최종 프로토타입으로 가지고 있다. Object.prototype은 기본적인 메서드와 속성을 포함하며, 모든 객체에 상속된다. 그래서, 대부분의 객체는 Object.prototype에 있는 메서드에 접근할 수 있다.
hasOwnProperty 메서드는 객체에 특정 속성이 직접 존재하는지 (즉, 프로토타입 체인을 통해 상속받은 속성이 아닌) 확인하기 위해 사용된다.
예를 들어, user 객체는 직접 name과 age 속성을 가지고 있다. 하지만 hasOwnProperty는 user 객체에 직접 존재하지 않는다. 대신 user 객체는 이 메서드를 Object.prototype에서 상속받아서 사용할 수 있다.
이렇게 프로토타입 체인을 통해 객체가 상위 프로토타입의 메서드와 속성에 접근하는 것은 JavaScript의 핵심적인 특징 중 하나이다.
프로토타입은 자바스크립트 객체가 다른 객체로부터 메서드와 속성을 상속받는 메커니즘을 말한다.
어떠한 오브젝트든(객체)지 그의 프로토타입 체인을 통해 Object.prototype의 메서드와 속성에 접근할 수 있다.
이렇게 하므로 인해서 더 적은 메모리를 사용할 수가 있고 코드를 재사용 할수 있다.
function Person(name, email, birthday) {
this.name = name;
this.email = email;
this.birthday = new Date(birthday);
this.calculateAge = function() {
const diff = Date.now() - this.birthday.getTime();
const ageDate = new Date(diff);
return Math.abs(ageDate.getUTCFullYear() - 1970);
}
}
const john = new Person('john', 'john@abc.com', '7-10-91');
const han = new Person('han', 'han@abc.com', '2-8-91');
console.log(john)
위 코드에서는 Person이라는 생성자(constructor) 함수를 정의하고 있다. 이 함수는 name, email, birthday라는 3개의 인자를 받아서 객체를 생성한다. 각각의 객체는 name, email, birthday라는 속성과 calculateAge라는 메서드를 가지게 된다.
그리고 john과 han이라는 두 개의 객체를 Person 생성자 함수를 사용하여 생성하고 있다.
function Person(name, email, birthday) {
this.name = name;
this.email = email;
this.birthday = new Date(birthday);
}
Person.prototype.calculateAge = function() {
const diff = Date.now() - this.birthday.getTime();
const ageDate = new Date(diff);
return Math.abs(ageDate.getUTCFullYear() - 1970);
}
const john = new Person('john', 'john@abc.com', '7-10-91');
const han = new Person('han', 'han@abc.com', '2-8-91');
console.log(john)
console.log(han)
이 코드는 Person 생성자 함수의 prototype 객체에 calculateAge 메서드를 추가한다. 이렇게 하면 Person을 통해 생성된 모든 객체는 calculateAge 메서드에 접근할 수 있다.
이 코드의 구체적인 내용은 다음과 같다.
const diff = Date.now() - this.birthday.getTime();: 현재 시간(밀리초)과 this.birthday.getTime() (생일 시간, 밀리초)의 차이를 구한다. Date.now()는 현재 시간을 밀리초로 반환하고, this.birthday.getTime()는 생일의 시간을 밀리초로 반환한다.
const ageDate = new Date(diff);: 시간 차이를 밀리초 단위로 new Date 객체에 전달하여 새로운 날짜 객체 ageDate를 생성한다.
return Math.abs(ageDate.getUTCFullYear() - 1970);: ageDate.getUTCFullYear() 메서드를 사용해 ageDate의 연도를 가져온 다음, 1970에서 빼서 절대값을 취한다. 이렇게 하면 현재 나이가 계산된다.
이렇게 prototype을 사용하면 여러 가지 이점이 있다.
calculateAge를 사용하는 방법은 다음과 같다.
const johnAge = john.calculateAge();
const hanAge = han.calculateAge();
console.log(`John's age is ${johnAge}`);
console.log(`Han's age is ${hanAge}`);
// John's age is 32
// Han's age is 32
Object.create() 메서드는 지정된 프로토타입 객체와 속성을 가진 새 객체를 생성한다. 이 메서드는 주로 프로토타입 기반 상속을 구현할 때 사용된다.
기본 사용법은 다음과 같다.
const newObj = Object.create(proto, [propertiesObject]);
const animal = {
type: 'Animal',
describe() {
return `An ${this.type} with ${this.legs} legs.`;
}
};
const dog = Object.create(animal, {
type: { value: 'Dog' },
legs: { value: 4, writable: true }
});
console.log(dog.describe()); // 출력: "An Dog with 4 legs."
위 예제에서 Object.create() 메서드를 사용하여 animal 객체를 프로토타입으로 가지는 새 dog 객체를 생성하였다. 이렇게 하면 dog 객체는 animal 객체의 속성과 메서드를 상속받게 된다.
propertiesObject는 선택적 인자로, 객체 리터럴과 유사한 형식을 사용하여 새로 생성되는 객체의 속성을 정의할 수 있다. 여기에서는 type과 legs 속성을 dog 객체에 추가하고 있다.
Object.create() 메서드는 상속 패턴, 캡슐화, 객체 생성에 유용하게 사용되며, 고급 객체 지향 프로그래밍 패턴을 구현할 때도 자주 활용된다.
function Person(name, email, birthday) {
let person = Object.create(personsPrototype);
person.name = name;
person.email = email;
person.birthday = new Date(birthday);
return person;
}
const personsPrototype = {
calculateAge() {
const diff = Date.now() - this.birthday.getTime();
const ageDate = new Date(diff);
return Math.abs(ageDate.getUTCFullYear() - 1970);
}
}
const john = new Person('john', 'john@abc.com', '7-10-91');
const han = new Person('han', 'han@abc.com', '2-8-91');
console.log(john)
console.log(han)
이 코드에서는 Person 함수를 사용하여 사람 객체를 생성한다. 그리고 personsPrototype 객체를 정의하여 공통된 메서드인 calculateAge를 추가한다. 이 메서드는 객체의 birthday 프로퍼티를 바탕으로 그 사람의 나이를 계산한다.
주의점
이 코드는 생성자 함수와 프로토타입을 사용하지 않고, 대신 Object.create 메서드를 사용하여 객체를 생성하고 프로토타입을 설정하는 패턴을 보여준다. 이러한 접근법은 생성자 함수와 new 연산자에 의존하지 않고도 객체 지향 프로그래밍을 할 수 있는 한 방법이다.