TIL 2020-12-09 (프로토타입)

nyongho·2020년 12월 9일
1

JavaScript

목록 보기
15/23
post-thumbnail

Prototype 을 좀 더 심층있게 파보자!


TIL List

  • Prototype 을 이루는 두 가지 요소
  • Object.create 메소드에 대한 이해
  • ES6 class 키워드 및 super 키워드 이용 방법

1) Prototype 을 이루는 두 가지 요소

우리가 보통 Prototype 이라고 부르는 개념에는 사실 아래 두 가지 요소가 포함 된 개념이다.

  1. Prototype Object

  2. Prototype Link

그렇다면 위 두 가지 개념들은 각각 어떤 역할을 하는지에 대해 알아보겠다.

1. Prototype Object

이전에서 객체를 생성할 때는 함수를 통해 생성하고 후에 특정 변수에 new 키워드를 사용해 생성자 역할을 부여한다고 언급했었다.

function MyInfo () {} // 함수 생성

let myinfo = new MyInfo(); // myinfo 변수에 MyInfo 함수를 부여해 객체 생성

그렇다면 우리는 지금까지 이러한 방식으로 객체를 선언했는가? 답은 아니다.

우리는 흔히 다음과 같은 방식으로 객체를 생성했었다.

let myInfo = {};

이렇게 간편하게 객체를 생성했는데 왜 위에서는 함수를 먼저 생성하고, 그 함수에 new 키워드를 사용해야만 객체를 생성할 수 있다고 했을까?

사실, 우리가 편하게 사용했던 위와 같은 방식은 사실 다음 과정을 통해 생성된 것과 같다.

let myInfo = new Object();

즉, myInfo 는 사실 Object 라는 함수를 통해 생성된 객체인 것이다.
(Object 는 자바스크립트에서 기본적으로 제공되는 함수로 Function, Array 도 마찬가지다.)

🔎 함수 생성시 발생하는 두 가지 일

1. 해당 함수에 Constructor (생성자) 자격을 부여한다.

생성자 자격이 부여된 함수만 new 키워드를 사용할 수 있다.

(obj1 은 함수를 생성한 것이 아니므로 new 키워드를 사용할 수 없는 모습이지만 obj2 는 함수를 생성했으므로 new 키워드 즉, 생성자 자격이 주어진다.)

2. 해당 함수의 Prototype Object 를 생성 하고 연결한다.

함수를 정의하면 함수 생성과 동시에 Prototype Object 도 함께 생성된다. 그렇기 때문에 우리는 Object 의 메소드들을 어떤 함수에서든지 사용할 수 있었던 것이다.

우리가 함수를 정의하면 Object 의 다양한 메소드를 사용할 수 있었던 이유 또한 여기서 설명이 된다.

(obj 함수를 생성함과 동시에 obj 의 prototype 에는 consturctor 와 proto 가 연결되어 있는 모습)

여기서 consturctor 는 생성되었던 함수 obj 를 가리키고 있다.

여기서 proto 는 Prototype Link 로 Object 를 가리키고 있다.

그렇다면 Prototype Link 는 뭘 의미하는지에 대해 알아보자.

function Person () {}

Person.prototype.eyes = 2;
Person.prototype.nose = 1;

let steve = new Person();
let mason = new Person();

console.log(steve.eyes);  // => 2

우리는 앞서 Person 이라는 함수를 생성하고 Person 의 prototype 에 eyes 라는 속성을 추가해줬다.

따라서 steve 라는 변수에 new 키워드를 이용함으로써 생성자 자격을 부여하고, Person 의 prototype에 접근할 수 있었다.

그렇다면 다음 코드를 한 번 봐보자.

function Person () {}

Object.prototype.eyes = 2;
Object.prototype.nose = 1;

let steve = new Person();
let mason = new Person();

console.log(steve.eyes);  // => 2

eyes 속성을 Person 함수가 아닌, Object 함수에 넣어줬는데도 정상적으로 eyes 값을 참조할 수 있는 모습이다. 이러한 방법이 어떻게 가능한 것일까?

여기서 __proto__ 의 개념이 나오게 되는데, 기본적으로 __proto__ 속성은 모든 객체가 빠짐없이 가지고 있는 속성으로 자신을 만들어낸 원형인 프로토타입 객체를 참조하는 숨겨진 링크로써 이는 결국 프로토타입을 의미하는 것이다.

(steve 의 __proto__ 를 열어보니 생성자는 Person 함수를 가리키고 있고 그 안의 __proto__ 를 열어보니 Object 를 가리키는 모습)

즉, 우리가 console.log(steve.eyes) 를 할 때 컴퓨터는 "어라? steve 는 eyes 라는 속성을 가지고 있지 않은데.. 그렇다면 연결되어있는 조상한테서 찾아봐야겠다!" 라는 생각을 가지고 상위 프로토타입(Person)을 탐색한다. Person 에서도 못 찾을 시 Person 에 연결된 또 다른 __proto__ 속성에 접근해 탐색한다. 이러한 방식을 반복하면서 최상위인 Object 의 Prototype Object 까지 탐색한다. 최상위까지 도달했는데도 못 찾을 경우 흔히 우리가 잘 아는 undefined 를 리턴한다.

이렇게 __proto__ 속성을 통해 상위 프로토타입과 연결되어 있는 형태를 프로토타입 체인(Prototype Chain) 이라고 부르고 우리는 이러한 형태 덕분에 그동안 상위의 상위 스코프에도 접근할 수 있었던 것이다.


2) Object.create 메소드의 이해

Object.create() 메서드는 지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듭니다.

Object.create 메소드는 ES5에서 도입된 것으로 새로운 객체를 만들 수 있는 메소드다. 현재 ES6 에서는 class 라는 보다 편한 문법이 도입되어 현재는 잘 사용되지 않는 메소드이지만, 이전에는 어떤 방식으로 프로토타입에 접근했는지 알기 위해 간단하게 짚고 넘어가자. 생성된 객체의 프로토타입은 이 메소드의 첫 번째 인수로 지정된다.

let Person = {
 eyes: 2
 nose: 1
}
let yongho = Object.create(Person); // yongho는 Person 의 prototype 을 상속받는다.
console.log(yongho.eyes); // => 2

단, 주의 할 점은 Object.create() 로 들어온 인자는 해당 프로토타입의 복사본으로 작용된다. 한마디로 원형 값에는 영향을 주지 않는다.

(원형값인 test 에는 영향을 주지 않는 모습)


3) ES6 에서의 class 및 super

이전에는 prototype 을 이용해 상위 함수의 프로토타입에 접근했다면, ES6 에서는 Class 및 super 라는 개념을 통해 다음과 같이 좀 더 간편하게 구현이 가능하다.

1. ES5 형식

ES5의 프로토타입 방식으로 간단하게 만들어 보겠다.

function Car (brand,name,color) {
 this.brand = brand;
 this.name = name;
 this.color = color;
 }
 
 Car.prototype.drive = function() {
   console.log(this.name + '가 운전을 시작합니다.'
 }
 
 let sonata = new Car('Hyundai', 'Sonata', 'Gray')
 sonata.brand; // 'Hyundai'
 sonata.drive(); // 'Sonata가 운전을 시작합니다.'

2. class 를 이용한 ES6 형식

이번에는 ES6에서 추가 된 class 문법을 사용해 만들어 보겠다.

class Car {
  constructor(brand,name,color) {
   this.brand = brand;
   this.name = name;
   this.color = color;
 }

drive() {
 console.log(this.name + '가 운전을 시작합니다.')
 }
}

let sonata = new Car('Hyundai', 'Sonata', 'Gray')
sonata.brand; // 'Hyundai'
sonata.drive(); // 'Sonata가 운전을 시작합니다.'

class 스코프 안의 메소드는 prototype 을 통해 연결을 해주지 않아도 참조할 수 있는 모습이다.

3. class 상속 및 super 개념

Car 라는 class 를 만들고 그 자식으로 다른 class 를 만들때 그 자식이 Car 의 속성에 접근할 수 있는 방법은 없을까?

다음의 키워드를 통해 이를 가능하게 할 수 있다. class 자식 extends 부모

그 다음, super 라는 키워드를 통해 부모의 생성자에 접근이 가능하다.

아래는 이에 대한 설명을 코드로 나타낸 것이다.

class Car { // 부모 Class
  constructor(brand,name,color) {
   this.brand = 'Hyundai'
   this.name = 'Sonata'
   this.color = 'Gray'
 }

drive() {
 console.log(this.name + '가 운전을 시작합니다.')
 }
}

class newMove extends Car { // newMove 라는 자식에게 부모인 Car 속성을 부여하기 위한 메소드
  constructor (name, color) { // 부모에게서 가져올 매개변수
    super(name, color) // super 키워드를 통해 일부분만 가져올 수도 있다!
  }
  drive() {
    console.log(this.name + ' 는 움직이고 있습니다.')
  }

  beat() {
    console.log(this.name + ' 가 충돌을 감지했습니다.')
  }
  whatColor() {
    console.log(`${this.name} 의 색상은 ${this.color} 색상 입니다.`)
  }
}

let Sonata = new newMove;

Sonata.drive()  /// => Sonata 는 움직이고 있습니다.
Sonata.beat() // => Sonata 가 충돌을 감지했습니다.
Sonata.whatColor() // => Sonata 의 색상은 Gray 색상 입니다.

자식의 생성자와 부모의 생성자가 동일하다면, 자식의 생성자는 아래와 같이 생략이 가능하다.

class newMove extends Car {
  constructor () { // 생략
    super()  // 이 경우 constructor 에도 매개변수를 입력하면 안된다.
  }
}

// 동일한 결과를 리턴

🚫 다음과 같이 super 키워드로만 매개변수를 받아올 경우 다음과 같은 문제점이 발생한다.

class newMove extends Car {
  constructor () {
    super(brand, name) 
  }
}

// super 는 기본적으로 constructor 에서 준 매개변수를 기반으로 작동한다.
// 따라서 constructor 에 아무값도 주지 않고 super 로만 특정 매개변수를 받아올 경우 
// "brand, name 은 정의된적이 없다!!!" 라는 에러를 나타낸다.

✅ 올바른 방법

class newMove extends Car {
  constructor (brand, name) { // 따라서 특정 값만 받아오고 싶은 경우 특정 값만 constructor 의 매개변수로 입력한 뒤
    super(brand, name)  // super 에 해당 매개변수를 입력하면 된다.
  }
}
profile
두 줄 소개

0개의 댓글