[웹 개발 기초 자바스크립트] 12. JavaScript Prototype

Shy·2023년 8월 29일
0

NodeJS(Express&Next.js)

목록 보기
13/39

JavaScript Prototype

JavaScript의 프로토타입 (Prototype)은 매우 중요한 개념이다. JavaScript는 프로토타입 기반 언어라고 불린다. 이는 객체 지향 언어의 일반적인 클래스 기반 상속과는 조금 다른 메커니즘을 사용한다.

JavaScript에서 모든 객체는 다른 객체를 참조하는 내부 링크, 즉 프로토타입을 가지고 있습니다. 이 프로토타입 객체로부터 객체는 메서드와 속성을 상속받을 수 있다.

주요 특징

  1. 프로토타입 연쇄 (Prototype Chain): 객체의 특정 속성 또는 메서드에 접근하려 할 때, 그 객체에 해당 속성/메서드가 없으면 자바스크립트는 객체의 프로토타입에서 그 속성/메서드를 찾는다. 프로토타입에도 해당 속성/메서드가 없다면 프로토타입의 프로토타입을 검사한다. 이 과정은 프로토타입 연쇄의 끝에 도달할 때까지, 즉 null 프로토타입에 도달할 때까지 반복된다.

  2. 공유 속성: 여러 객체가 같은 프로토타입을 참조하면 해당 프로토타입의 속성과 메서드를 공유한다. 따라서 프로토타입에 정의된 메서드나 속성은 모든 객체에서 사용할 수 있다.

  3. 확장성: 프로토타입을 사용하면 기존 객체를 확장하거나 변경하는 것이 쉽다. 예를 들어, 모든 배열 객체에 새로운 메서드를 추가하려면 Array.prototype에 메서드를 추가하면 된다.

예제

예제1

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의 프로토타입은 객체 간에 속성과 메서드를 효율적으로 공유하고, 동적으로 객체를 확장하거나 수정하는 데 유용한 메커니즘을 제공한다.

예제2

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의 메서드와 속성에 접근할 수 있다.

이렇게 하므로 인해서 더 적은 메모리를 사용할 수가 있고 코드를 재사용 할수 있다.

예제3

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라는 메서드를 가지게 된다.

  1. this.name = name; - name이라는 속성을 생성하고 인자로 받은 name의 값을 저장한다.
  2. this.email = email; - email이라는 속성을 생성하고 인자로 받은 email의 값을 저장한다.
  3. this.birthday = new Date(birthday); - birthday이라는 속성을 생성하고, new Date(birthday)를 통해 인자로 받은 birthday의 값을 Date 객체로 변환하여 저장한다.
  4. this.calculateAge = function() {...} - calculateAge라는 메서드를 생성하고, 이 메서드가 호출되면 현재 날짜와 birthday 사이의 시간 차이를 계산하여 나이를 반환한다.

그리고 john과 han이라는 두 개의 객체를 Person 생성자 함수를 사용하여 생성하고 있다.

예제4 (Prototype생성)

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 메서드에 접근할 수 있다.

이 코드의 구체적인 내용은 다음과 같다.

  1. const diff = Date.now() - this.birthday.getTime();: 현재 시간(밀리초)과 this.birthday.getTime() (생일 시간, 밀리초)의 차이를 구한다. Date.now()는 현재 시간을 밀리초로 반환하고, this.birthday.getTime()는 생일의 시간을 밀리초로 반환한다.

  2. const ageDate = new Date(diff);: 시간 차이를 밀리초 단위로 new Date 객체에 전달하여 새로운 날짜 객체 ageDate를 생성한다.

  3. return Math.abs(ageDate.getUTCFullYear() - 1970);: ageDate.getUTCFullYear() 메서드를 사용해 ageDate의 연도를 가져온 다음, 1970에서 빼서 절대값을 취한다. 이렇게 하면 현재 나이가 계산된다.

이렇게 prototype을 사용하면 여러 가지 이점이 있다.

  • 메모리를 효율적으로 사용할 수 있습니다. calculateAge 메서드는 모든 객체에 대해 하나의 복사본만 생성되므로 메모리 사용량이 줄어든다.
  • Person 생성자를 통해 만든 모든 객체에서 이 메서드를 사용할 수 있다. 이는 코드의 재사용성을 높여준다.
  • Person 생성자의 원래 정의를 변경하지 않고도, 기능을 추가하거나 수정할 수 있다.
    이러한 방식은 JavaScript에서 객체지향 프로그래밍의 중요한 부분 중 하나이다.

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()

Object.create() 메서드는 지정된 프로토타입 객체와 속성을 가진 새 객체를 생성한다. 이 메서드는 주로 프로토타입 기반 상속을 구현할 때 사용된다.

기본 사용법은 다음과 같다.

const newObj = Object.create(proto, [propertiesObject]);
  • 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() 메서드는 상속 패턴, 캡슐화, 객체 생성에 유용하게 사용되며, 고급 객체 지향 프로그래밍 패턴을 구현할 때도 자주 활용된다.

예제5(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 프로퍼티를 바탕으로 그 사람의 나이를 계산한다.

  1. personsPrototype 객체는 calculateAge라는 메서드를 가지고 있다.
  2. Person 함수는 name, email, birthday 매개변수를 받아서 새로운 객체를 생성한다.
    • Object.create(personsPrototype)를 통해 personsPrototype을 프로토타입으로 가지는 새 객체를 생성한다.
    • 그 다음, name, email, birthday 프로퍼티를 설정하고, birthday는 Date 객체로 변환된다.
    • 마지막으로, 생성된 person 객체를 반환한다.
  3. john과 han이라는 두 개의 객체를 Person 함수를 사용하여 생성한다.
  4. 두 객체는 console.log로 출력된다.

주의점

  • new Person(...) 형태로 호출되지만, Person 함수는 사실상 일반 함수이다. new 키워드는 여기서는 특별한 의미가 없다. new 없이 Person('john', 'john@abc.com', '7-10-91') 이렇게 호출해도 동일한 결과가 나올 것이다.
  • Person 함수 내부에서 Object.create(personsPrototype)를 호출하므로, Person 함수를 호출하여 생성된 각 객체 (john, han)는 personsPrototype을 자신의 프로토타입으로 가진다. 따라서 john.calculateAge() 또는 han.calculateAge() 형태로 calculateAge 메서드를 호출할 수 있다.
  • 함수 Person으로 객체person을 생성하므로 헷갈릴 수 있다. 하지만 JavaScript는 대소문자를 구분하는 언어이므로 Person과 person은 서로 다른 식별자로 취급된다. Person은 이 경우에 함수의 이름으로 사용되며, person은 함수 내부에서만 사용되는 지역 변수이다.

이 코드는 생성자 함수와 프로토타입을 사용하지 않고, 대신 Object.create 메서드를 사용하여 객체를 생성하고 프로토타입을 설정하는 패턴을 보여준다. 이러한 접근법은 생성자 함수와 new 연산자에 의존하지 않고도 객체 지향 프로그래밍을 할 수 있는 한 방법이다.

profile
초보개발자. 백엔드 지망. 2024년 9월 취업 예정

0개의 댓글