카테고리 없음

Section 29: 프로토타입, 클래스, OOP

프로아마추어 2022. 5. 1. 19:17

1. 프로토타입이란?

  • JavaScript는 프로토타입 기반 언어라고 불린다.
  • JavaScript의 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로 프로토타입 객체를 가진다는 의미이다.
  • 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지이다.
  • 이를 프로토타입 체인(prototype chain)이라 부르며 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 한다.
  • 정확히 말하자면 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 prototype이라는 속성에 정의되어 있다.
  • JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결(많은 브라우저들이 생성자의 prototype 속성에서 파생된 __proto__ 속성으로 객체 인스턴스에 구현하고 있다.)이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메소드를 탐색한다.

배열의 예를 들어보자.

const arr = [1, 2, 3];
arr.sing = () => {console.log('LA LA LA')};

console창을 열어 arr배열을 찍어보면 arr의 index와 숫자형 값, 우리가 만든 sing 메서드를 확인할 수 있다.
prototype을 보면 모든 배열들이 접근할 수 있는 메서드들이 담겨있는 것을 확인할 수 있다.

다른 배열 arr2를 만들어보자.

const arr2 = [4, 5, 6];

arr2를 console창으로 확인해보면 arr과 마찬가지로 __prototype라는 특성을 공유하고 있다.
따라서 우리가 선언한 모든 배열들은 prototype을 참조해 메서드에 접근한다는 사실을 알 수 있다.

 

body값에도 HTMLBodyElement의 특성을 갖는 prototype이 있다.

const body = document.body;
console.dir(body); // [[Prototype]]: HTMLBodyElement

 

배열의 prototype의 기존 메서드를 고쳐보자.

Array.prototype.pop = function () {
  return '지우지마세요!!!';
};

[1,2,3].pop() // 지우지마세요!!!

 

문자열의 prototype에 직접 접근해보자.
원시 타입인데도 불구하고 wrapper object로 모든 문자열 메서드에 접근할 수 있다.

String.prototype

 

새로운 메서드를 만들어보자.

String.prototype.sing= () => alert('랄랄랄랄라~');
우리가 만든 새로운 문자열 메서드에 접근한다.
'bird'.sing(); // 랄랄랄랄라~

 

2. 객체 지향 프로그래밍 개요

  • 지난 section에서 XMLHttpRequest를 다뤘었다.
  • new keword를 통해 객체를 만들고 계속해서 객체를 찍어내는 것도 가능하다.
// XMLHttpRequest객체를 만든다.
//req는 다양한 메서드에 접근하는 것이 가능하다.
const req = new XMLHttpRequest();

 

3. 팩토리 함수

  • 객체를 반환하는 형태의 함수.
function makeColor(r, g, b) {
  // 빈 객체 생성
  const color = {};
  // 속성 추가
  color.r = r;
  color.g = g;
  color.b = b;
  // 메서드 추가
  color.rgb = function () {
    const { r, g, b } = this;
    return `rgb ${r} ${g} ${b}`;
  };
  color.hex = function () {
    const { r, g, b } = this;
    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  };
  return color;
}

// 팩토리 함수 호출
const firstColor = makeColor(35, 255, 150);
// 메서드 접근이 가능하다.
firstColor.rgb();
firstColor.hex();

 

4. 생성자 함수

  • 팩토리 함수를 사용하지 않는 이유와 생성자 함수를 사용하는 이유를 확인해보자
// 팩토리 함수의 예제에서 두 객체를 만들었다.
const firstColor = makeColor(35, 255, 150);
const black = makeColor(0, 0, 0);

// 함수 또한 참조값을 가진다. 따라서 두 객체는 각각 개별적으로 함수를 부여받았다는 것을 알 수 있다.
firstColor.hex === black.hex // false

// 문자열의 함수는 각 객체에 따로 부여 받지 않았다.
// prototype 객체에 접근해 메서드를 사용하기 때문이다.
'hello'.slice === 'bye'.slice // true

color1.hex() // '8241e7'
color2.hex() // '#ffffff'

// prototype에 함수가 잘 들어간 것을 알 수 있다.
color1.hex === color2.hex // true

 

생성자 함수를 만들어보자

// this keyword를 사용해 window 객체가 아닌 Color객체를 가르키자
function Color(r, g, b) {
	this.r = r;
	this.g = g;
	this.b = b;
}

Color.prototype.rgba = function (a = 1.0) {
  const { r, g, b } = this;
  return `rgba(${r},${g},${b},${a})`;
};

const color1 = new Color(130, 65, 231);
const color2 = new Color(255, 255, 255);

// 웹 페이지 body의 배경색이 변하는 것을 알 수 있다.
document.body.style.backgroundColor = colo1.rgba(0.5);

 

5. JavaScript class

  • Class는 객체를 생성하기 위한 템플릿이다.
// class 선언
// class 이름과 생성자 함수 이름의 첫번째 글자는 대문자로 처리한다.
class Color {
  // Color객체가 생성될 때 마다 즉시 실행되는 함수.
  constructor(r, g, b, name) {
    // new keyword를 통해 this는 새로운 객체를 참조한다.
    this.r = r;
    this.g = g;
    this.b = b;
    this.name = name;
  }
  // 메서드 선언
  innerRGB() {
    const { r, g, b } = this;
    return `${r},${g},${b}`;
  }
  rgb() {
    return `rgb(${this.innerRGB()})`;
  }
  hex() {
    const { r, g, b } = this;
    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }
  rgba(a = 1.0) {
    return `rgba(${this.innerRGB()},${a})`;
  }
}

// new keyword와 함께 class이름을 선언해준다.
// 객체가 잘 생성되었음을 확인할 수 있다.
const c1 = new Color(199, 0, 57, 'prettyRed');

document.body.style.backgroundColor = c1.rgba(0.8);

 

6. 클래스 연습 더 알아보기

class Color {
  constructor(r, g, b, name) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.name = name;
    // 생성자에 함수를 선언한다.
    // 객체가 생성되면 바로 해당 함수가 호출된다.
    this.calcHsl();
  }
  innerRGB() {
    const { r, g, b } = this;
    return `${r},${g},${b}`;
  }
  rgb() {
    return `rgb(${this.innerRGB()})`;
  }
  hex() {
    const { r, g, b } = this;
    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }
  rgba(a = 1.0) {
    return `rgba(${this.innerRGB()},${a})`;
  }
  fullySaturated() {
    const { h, l } = this;
    return `hsl(${h},100%,${l}%)`;
  }
  opposite() {
    const { h, s, l } = this;
    const newH = (h + 180) % 360;
    return `hsl(${newH},${s}%,${l}%)`;
  }
  hsl() {
    const { h, s, l } = this;
    return `hsl(${h},${s}%,${l}%)`;
  }
  calcHsl() {
    let { r, g, b } = this;
    (r = r / 255), (g = g / 255), (b = b / 255);
    // get the min and max of r,g,b
    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    // lightness is the average of the largest and smallest color components
    var lum = (max + min) / 2;
    var hue;
    var sat;
    if (max == min) {
      // no saturation
      hue = 0;
      sat = 0;
    } else {
      var c = max - min; // chroma
      // saturation is simply the chroma scaled to fill
      // the interval [0, 1] for every combination of hue and lightness
      sat = c / (1 - Math.abs(2 * lum - 1));
      switch (max) {
        case r:
          // hue = (g - b) / c;
          // hue = ((g - b) / c) % 6;
          // hue = (g - b) / c + (g < b ? 6 : 0);
          break;
        case g:
          hue = (b - r) / c + 2;
          break;
        case b:
          hue = (r - g) / c + 4;
          break;
      }
    }
    hue = Math.round(hue * 60); // °
    sat = Math.round(sat * 100); // %
    lum = Math.round(lum * 100); // %
    this.h = hue;
    this.s = sat;
    this.l = lum;
  }
}

const c1 = new Color(199, 0, 57, 'prettyRed');
const c2 = new Color(151, 245, 225, 'prettyGreen');

 

 

7. extends & super 키워드

  • 부모-자식 클래스와 상속에 연관이 있다.

예제 1: class 준비

// cat 클래스와 dog클래스를 만들어두자.
class Cat {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  eat() {
    return `${this.name} 밥 먹는다!!`;
  }
  meow() {
    return '미야옹!';
  }
}

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  eat() {
    console.log(`${this.name} 밥 먹는다!!`);
  }
  bark() {
    return '크르르르릉!!';
  }
}

예제2: extends & super

// Cat과 Dog 클래스로부터 공통분모를 Pet 클래스로 옮겨준다.
class Pet {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  eat() {
    return `${this.name}은(는) 냠냠쩝쩝`;
  }
}

// Pet 클래스를 상속받는다.
class Cat extends Pet {
  // super는  super클래스의 생성자를 따른다.
  constructor(name, age, character) {
    super(name, age);
    this.character = character;
  }
  meow() {
    return '미야옹!';
  }
}

// Pet 클래스를 상속받는다.
class Dog extends Pet {
  bark() {
    return '크르르르릉!!';
  }
  eat() {
    // 자식 클래스에서 같은 이름의 함수의 값을 변경할 수 있다.
    return `${this.name}은 후루루룩챱챱!`;
  }
}

// 부모 클래스 Pet의 name, age, eat함수까지 접근 가능하다.
const kitten = new Cat('쫑쫑', 1);

kitten;
// Cat {name: '쫑쫑', age: 1}
kitten.eat();
// '쫑쫑은(는) 냠냠쩝쩝'

const doggy = new Dog('왈왈', 2);
doggy.eat();
// '왈왈은 후루루룩챱챱!'