TIL 25 | JavaScript 객체 지향 프로그래밍 02

생활코딩 객체 지향 프로그래밍 수업을 보며 정리한 내용입니다.


어제는 객체를 어떻게 만들고 어떻게 쓸 수 있는지를 배웠다. 이제 객체에 대해 이해한것을 바탕으로 조금 더 심화된 내용을 공부해보려고 한다!

1. constructor

1-1. constructor의 필요성

전에 만들었던 것처럼 그냥 객체를 만들어보면,

const kim = {
  name: "kim",
  first: 10,
  second: 20,
  sum: function () {
    return this.first + this.second;
  },
};

const lee = {      //다른사람을 또 하나 만들어본다.
  name: "lee",
  first: 10,
  second: 10,
  sum: function () {
    return this.first + this.second;
  },
};

console.log(kim.sum());  //30
console.log(lee.sum());  //20

아쉬운 점 : 만약 객체가 동작하는 방법이 바뀌어서(예_ third가 추가된다면) 수정해야 한다면, 이것뿐만 아니라 또 같은 형태의 객체들 모두에게 적용해주어야 한다. 만약 이런 작업을 1억개에게 해줘야 한다면,,? 🤦🏻‍♀️

const kim = {
  name: "kim",
  first: 10,
  second: 20,
  third: 30,    				   //여기도 추가해주고
  sum: function () {
    return this.first + this.second + this.third;  //여기도 추가해주고
  },
};

const lee = {    
  name: "lee",
  first: 10,
  second: 10,
  third: 10,    				   //여기도 추가해주고
  sum: function () {
    return this.first + this.second + this.third;  //여기도 추가해주고
  },
};

console.log(kim.sum());  //60
console.log(lee.sum());  //30

→ 즉, 이렇게 객체의 기본 동작방법이 바뀐다면 같은 취지의 객체는 이렇게 다~~~ 바꿔줘야 한다는 것이다.

👏🏻 그렇기 때문에 이런 형식의 객체를 찍어내는 공장(constructor)을 만들거고, 이제 그 공장을 이용해서 객체를 양산해보자!! ✨


1-2. constructor의 사례

  • JavaScript에는 시간을 알아내고 싶을 때 사용하는 Date 라는 것이 있다.
  • 그리고 이 Date 를 이용할때에는 new 라는 키워드를 붙여주고
  • 그리고 이 Date 의 (괄호) 안에다가 현재날짜를 적어준다.
  • 그다음 d1 이라고 하게되면은
    => 내부적으로 2020년 12월 28일이라고 하는 데이터를(상태를) 가지고 있는 새로운 객체가 만들어져서 d1이 된다.
  • (비교) 위에서 우리가 만든 const lee, const kim 은 객체의 설계도가 다 노출이 되는데
  • Date( ) 를 보면 이걸 통해 최종적으로 만들어진 객체가 어떤 설계도를 가지고 있는지 우리 눈엔 보이지 않는다.
  • 하지만 그럼에도 우린 d1 이라는 객체를 사용할 수 있다.
const d1 = new Date("2020-12-28");

console.log(d1.getFullYear()); //그 객체에게 연도를 물어본거다.    ... 2020
console.log(d1.getMonth());// 11 이 나오는데 ... month는 0부터 count 되기 때문이다. 

const d1 = new Date("2020-12-28"); ← 이걸 보면 함수를 호출하는 것처럼 보이죠? 👀 .. 맞다! 왜냐하면 저 Date가 함수이기 때문이다!

console.log(Date); 를 출력해보면 아래사진처럼 나오는데, 풀이를 해보면 ... Date는 함수고, native code라는건 내장된 함수라는 뜻이다.


1-3. constructor 만들기

이제 그럼, Date 처럼 객체를 찍어내는 공장을 만들어보자 🙂

function Person() {
    this.name = "kim";
    this.first = 10;
    this.second = 20;
    this.third = 30;
    this.sum = function () {
      return this.first + this.second + this.third;
    };
}

→ 그리고 방금 만든 Person 이라는 함수를 직접 호출해보자!

console.log(Person());  //undefined

🤔 왜 undefined 가 나올까? => 왜냐하면 Person 함수는 아무것도 return하고 있지 않기 때문이다.


🤚🏻 그런데 Person 앞에다가 new 라는 키워드를 붙여보게 되면? 이제 Person 이라는 함수는 완전히 다른 존재가 된다!

console.log(new Person());    //Person {name: "kim", first: 10, second: 20, third: 30, sum: f}

→ 결과는 Person 이라는 객체가 만들어졌다! .. 이객체의 속성은 name은 kim, first는 10 .... sum이라는 함수까지!

  • 정리해보면 함수를 그냥 호출하면 그건 그냥 함수이다.
  • 그런데 앞에 new 라는 키워드를 붙이면 이제 이 함수는 더이상 일반적인 함수가 아니라!!
  • 객체를 생성하는 생성자가 되는 것이다!!! 그럼 생성자가 영어로는? constructor!
  • 그러니까 new Person( )처럼 앞에 new 가 붙어있으면! 맥락적으로 얘를 생성자함수라고 부른다는 것도 기억하자!

그럼 이제 어떻게 변화하는지 한번 보자!

const kim2 = new Person();
const lee2 = new Person();

그리고 실행했을때 어떻게 되는지도 확인해보자!

console.log(kim2.sum()); //60
console.log(lee2.sum()); //60

🤔 ... 둘 다 60이 나오는데, 그 이유는 뭘까?
kim2에 있는 new Person이나, lee2 에 있는 new Person 모두가 내부적으로 같은 상태 를 가지고 있어서 그렇다.


👏🏻 그럼 이제 난 뭘 하고 싶을까?
Date 객체처럼 (const d1 = new Date("2020-12-28");),
constructor 함수가 실행될 때 입력값을 주입할 수 있던 것과 같이 우리도 할 수 있다! 그걸 해보자!

function Person3(name, first, second, third) {
    this.name = name;
    this.first = first;
    this.second = second;
    this.third = third;
    this.sum = function () {
      return this.first + this.second + this.third;
    };
}
const kim3 = new Person3("kim", 10, 20, 30);
const lee3 = new Person3("lee", 10, 10, 10);

console.log(kim3.sum());  //60
console.log(lee3.sum());  //30

이게 바로 객체를 찍어내는 공장인 constructor function 을 만들어내는 방법이다.


😇✨ 그래서 이 constructor function 을 만들어서 생기는 좋은점은!

  • 이전에는 중괄호를 통해서 객체를 만들때마다 그 객체를 다시 정의해줬어야 했는데
const kim = {
  name: "kim";
  first: 10;
  second: 20;
  sum: function () {
    return this.first + this.second;
  };
};   //.. 이렇게
  • 이제 우리가 이 constructor function 을 만들게되면, 앞에 new 를 사용함으로써.. 실행할 때마다 객체가 양산된다.
  • 그리고! constructor function 의 내용을 바꾸면 이 constructor function 을 사용해 만들어진 모든 객체가 한.번.에 바뀌는 폭발적인 효과를 얻게된다. 🥳 🎉

그럼 이제 constructor function무언인지 / constructor function 을 쓰면 뭐가 좋은지 를 알게 되었다! 🙂



2. prototype

prototype 은 한국어로는 '원형' 으로 번역될 수 있는 말이다. 어떤 사물의 공통된 모습? 또는 본래의 모습?

자바스크립트에서 prototype 이라는 것은? JavaScript을 object-oriented language(객체지향언어) 라고 했듯이 또 prototype based language (프로토타입 기반 언어) 라고도 한다. 이렇게 불리듯! 그만큼 프로토타입이라는 것은 자바스크립트를 지탱하는 기반이라고 할 수 있다.

2-1. prototype이 필요한 이유

function Person(name, first, second, third) {   //Person이라고 하는 함수가 이거 *
    this.name = name;
    this.first = first;
    this.second = second;
    this.third = third;
    this.sum = function () { 
      return this.first + this.second + this.third;
    };
}

const kim = new Person("kim", 10, 20, 30);  //kim이라는 객체를 생성하는게 이거 *
const lee = new Person("lee", 10, 10, 10);

constructor function 을 이용해서 여기까지 잘 만들어봤지만 그래도 아직 아쉬운 부분이 있다.
지금 보면 kim이라는 객체∗ 를 생성할 때 Person이라고 하는 함수∗ 를 생성자로써 동작시켰다. 그런데 이 생성자 함수 안에 this.sum 이라는 함수가 들어있기 때문에 객체가 생성될 때마다 계속 함께 만들어져 실행 되므로 컴퓨터 메모리가 낭비된다. 뿐만 아니라 만약 극단적으로 엄청 많은 객체를 생성한다고 가정하면, 이 함수를 생성하는데에도 많은 시간이 소요가 될 것이고 이건 성능을 저하시키는 요소가 될 수 있다.
🚨 그렇기 때문에 이 부분을 수정해야 한다!

2-2. prototype을 이용해서 재사용성 높이기

프로토타입 이라는 개념을 이용해서 생성자를 통해 만든 객체들 모두가 공통적으로 사용하는 속성을 만드는 방법을 배워보자.

생성자 함수에서 공통적으로 사용할 method를, prototype 을 이용해 따로 빼서 만들어준다.

function Person(name, first, second, third) {
    this.name = name;
    this.first = first;
    this.second = second;
    this.third = third;
}

//Person이라는 생성자 함수에 공통적으로 사용할 sum이라는 method를 이렇게 만든다!
Person.prototype.sum = function () {
  //그럼 여기안에서 수정해도 Person을 통해 만든 객체 전부에게 적용된다.
  return this.first + this.second + this.third ;
}; 
// 출력해보면,
const kim = new Person("kim", 10, 20, 30);
const lee = new Person("lee", 10, 10, 10);

console.log(kim.sum()); //60
console.log(lee.sum()); //30

→ 출력되는 결과는 같지만! 이렇게 하면 차이점이 있다.

  • Person이라는 생성자 함수(constructor) 안에 sum을 정의한 것이 들어가 있지 않기 때문에 객체가 만들어질 때마다 실행되지 않고 한번 실행된다.
    : 그럼 한번만 정의되는 것이기 때문에 성능과 메모리를 절약할 수 있다.
  • 그리고 하나의 함수를 수많은 객체들이 share할 수 있다. (prototype을 이용해 만든 함수 한곳에서만 수정해도 전부 적용된다.)
    : 이를 통해서도 역시 성능과 메모리를 절약할 수 있다.

같이 만들어진 수많은 객체들중에서 하나의 객체의 method만 수정하는 것도 가능하다!

kim.sum = function () {
  return "this method has changed!";
};

console.log(kim.sum());  //this method has changed!
console.log(lee.sum());  //30

🤔 이렇게 변수 kim이 가리키는 객체의 sum method만 다르게 동작시킬 수 있는 이유는? (프로토타입의 특징이다.)
→ 자바스크립트는 kim이라는 객체의 sum 메소드를(kim.sum()) 호출 할 때 제일 먼저 그 객체 자신이 sum 이라고 하는 속성을 가지고 있는지를 찾는다. (= 그 속성이 지금 여기에선 함수) 그렇기 때문에 kim.sum = function () { return "this method has changed!"; }; 을 실행을 시키고, 거기서 끝을 내는거다.

그런데 lee라고 하는 객체는, 이 객체를 생성const lee = new Person("lee", 10, 10, 10); 한 이후에 sum이라는 어떤 method를 정의한 적이 없기때문에! JavaScript는 우리가 찾는 이 sum을(lee.sum()) lee라고 하는 객체 자신이 가지고 있지 않으면 이 객체 생성자인 Person의 prototype이라고 하는 것의 sum이라고 하는 method가 정의되어 있는지 찾고, 그게 찾아지면 그걸 실행하는 것이다.

이제 이 질문들에 대답할 수 있다 🙂

  • 프로토타입이란 어떤 의미가 있는지
  • 우리가 프로토타입을 사용하지 않고 이 생성자 함수 안에서 method 나 속성을 직접 정의하게 되면 어떤 비효율이 생기는지
  • 그러면 그 비효율을 프로토타입을 통해서 어떻게 극복했는지

좋은 웹페이지 즐겨찾기