클래스, 프로토타입, 객체지향, this

HongBoogie·2024년 8월 8일

클래스란?

  • ES6에서 등장한 문법으로, 좀더 JAVA스럽게, 객체 지향적으로 표현하기 위해 추가된 새로운 문법.
  • 기존에는 프로토타입 체이닝을 통해 클래스랑 비슷하게 구현했으나, 객체 지향 프로그래밍이 익숙한 프로그래머들에게 큰 혼란을 줬음. 또한 유지보수가 어렵고 상속도 어려워서 클래스 문법의 등장은 필연적이었다.
const Meal = function(food) {
	this.food = food
}

Meal.prototype.eat = function() {
	return 'great!'
}
// old

class Meal {
	constructor (food) {
		this.food = food
	}
	eat() {
		return 'great!'
		}
}
// new

두 문법은 내부적으로 완전히 같은 구조다.

// 클래스
class Person {
   // 이전에서 사용하던 생성자 함수는 클래스 안에 `constructor`라는 이름으로 정의합니다.
   constructor({name, age}) { //생성자
     this.name = name;
     this.age = age;
   }
   // 객체에서 메소드를 정의할 때 사용하던 문법을 그대로 사용하면, 메소드가 자동으로 `Person.prototype`에 저장됩니다.
   introduce() {
     return `안녕하세요, 제 이름은 ${this.name}입니다.`;
   }
}

const person = new Person({name: '외데고르', age: 19});
console.log(person.introduce()); // 안녕하세요, 제 이름은 외데고르입니다.
  • constructor는 인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수한 메서드이다.
  • constructor는 클래스 안에 한 개만 존재할 수 있다. 2개 이상 있을 경우 Syntax Error가 발생하니까 주의
  • JAVA나 Python의 클래스 문법과의 차이점은, 자바스크립트의 클래스 문법에선 인스턴스 변수를 반드시 지정하지 않고 생성자(constructor)을 통해 this.변수 문법으로 자동 생성될수 있다는 점이다.

객체지향 프로그래밍의 상속과 다형성

상속

  • 한 클래스가 다른 클래스의 속성과 메서드를 물려받는 것
  • 예시
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

// Animal을 상속받아 생성자를 그대로 사용하는 모습.
class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

let dog = new Dog('Rex');
dog.speak();// 출력: Rex barks.

다형성

  • 같은 인터페이스를 사용하는 객체가 다양한 형태를 가질 수 있는 특성.
  • 종류
    1. 오버라이딩(Overriding): 부모 클래스의 메서드를 자식 클래스에서 재정의
    2. 오버로딩(Overloading): 같은 이름의 메서드를 다른 매개변수로 정의 (JavaScript에서는 직접적으로 지원하지 않음)
  • 예시 (오버라이딩)
class Shape {
  area() {
    return 0;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

// 둘다 Shape 를 상속받았지만 생성자나 내부 함수의 형태는 다르다.
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

let shapes = [new Circle(5), new Rectangle(4, 5)];
shapes.forEach(shape => console.log(shape.area()));

Class와 Prototype 비교

Class

  • ES6에서 도입된 문법적 설탕(Syntactic sugar).
  • 내부적으로는 여전히 프로토타입 기반으로 동작.
  • 정적 메서드, getter, setter 등을 쉽게 정의할 수 있다.

Prototype

  • JavaScript의 객체 지향 프로그래밍의 기본 메커니즘.
  • 모든 객체는 프로토타입을 가지며, 이를 통해 속성과 메서드를 상속받는다.
  • 메모리 관리가 매우 효율적으로, 모든 인스턴스가 프로토타입의 메서드를 공유한다.

class

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

let person = new Person('John');
person.sayHello();// 출력: Hello, I'm John

Prototype

function Person(name) {
  this.name = name;
}

// class와는 다르게 prototype을 통해 내부 함수를 지정해줌.
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

let person = new Person('John');
person.sayHello();// 출력: Hello, I'm John// 런타임에 메서드 추가
Person.prototype.sayGoodbye = function() {
  console.log(`Goodbye from ${this.name}`);
};

person.sayGoodbye();// 출력: Goodbye from John

this와 super 키워드 차이

this

  • 컨텍스트에 따라 다르게 바인딩됨.
    1. 일반 함수 호출에서는 전역 객체 (strict mode에서는 undefined)
    2. 메서드 호출에서는 메서드를 소유한 객체
    3. 생성자 함수에서는 새로 생성된 객체
    4. 화살표 함수에선 외부 스코프의 this

super

  • 부모 클래스의 생성자, 메서드를 호출할 때 사용한다.
  • 정적 메서드 내에서 부모 클래스를 참조할 때 사용한다.

this

class Person {
  constructor(name) {
    this.name = name;
  }

  introduce() {
    console.log(`My name is ${this.name}`);
  }
}

let person = new Person('Alice');
person.introduce();// 출력: My name is Alice

super

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);// 부모 클래스의 생성자 호출
    this.breed = breed;
  }

  speak() {
    super.speak();// 부모 클래스의 메서드 호출
    console.log(`${this.name} barks.`);
  }

  introduce() {
    console.log(`I'm ${this.name}, a ${this.breed}`);
  }
}

let dog = new Dog('Rex', 'German Shepherd');
dog.speak();
// 출력: Rex makes a sound. Rex barks.

dog.introduce();// 출력: I'm Rex, a German Shepherd

JS 화살표 함수와 this 바인딩

화살표 함수란?

ES6에서 등장한 새로운 문법으로

  • 익명함수로만 사용 가능
  • 메소드나 생성자 함수로 사용 불가능

하다는 특징이 있다.

// 기존 함수
const foo = function () {
  console.log('기존 함수');
}

// 화살표 함수
const foo = () => console.log('화살표 함수');

그렇다면 이걸 왜쓰냐? this 바인딩에 차이가 있기 때문이다.

JS 함수의 this 바인딩은?

굉장히 묘한데, JS에서의 this는 상황에 따라 다르게 바인딩된다.

대표적으로

  1. 전역 공간에서의 this : 전역 객체
  2. 메소드(class 및 객체에 종속) 호출시 내부의 this: 메소드를 호출한 객체
  3. 함수(독립적으로 존재) 호출 시 함수 내부의 this : 지정되지 않는다.

지정되지 않음 = this는 전역 객체를 바라본다는 뜻이다. 이게 뭔말이냐 하면

const Arsenal = {
  captain: 'Odegaard',
  foo1: function() {
    const foo2 = function() {
      console.log(this.captain);
    }
    foo2();
  }
};

Arsenal.foo1();	// undefined

foo1을 실행하게 되면 내부 함수 foo2가 실행되면서 this.captain을 호출하는데, foo2 내부에 this가 지정되지 않아서 전역 객체를 가리킨다. 즉, 전역 객체엔 captain이 없으므로 undefined가 출력된다.

하지만!

const Arsenal = {
  captain: 'Odegaard',
  foo1: function() {
    const foo2 = () => {
      console.log(this.captain);
    }
    foo2();
  }
};

Arsenal.foo1();	// Odegaard

화살표 함수로 this를 출력하면 제대로 동작이 된다!

이유인 즉슨, 화살표 함수로 선언한 함수에는 this가 없어서, 상위 환경으로 거슬러 올라가다 우리가 지정한 this를 참조한다는 것이다. 다시 말해 선언될 시점에서의 상위 스코프가 this로 바인딩됩니다.

객체 지향이란?

추상화란?

  • 현실 속에서 우리에게 필요한 본질만 골라내는 것
  • 불필요한 부분을 무시함으로써 현실에 존재하는 복잡성을 극복하는 것

데이터 타입

  • 메모리 안에 저장된 데이터의 종류를 분류하는데 사용하는 메모리 집합에 대한 메타데이터
  • 암시적으로 어떤 종류의 연산이 해당 데이터에 대해 수행될 수 있는지를 결정

전통적인 데이터 타입의 개념은 객체의 타입에도 그대로 적용된다.

  1. 어떤 객체가 어떤 타입에 속하는지를 결정하는 것은 객체가 수행하는 행동이다. 동일한 행동을 수행할 수 있다면 동일한 타입으로 분류될 수 있다.
  2. 객체의 내부적인 표현은 외부로부터 철저하게 감춰진다.

즉, 객체의 내부 표현 방식이 다르더라도 어떤 객체들이 동일하게 행동한다면 그 객체들은 동일한 타입에 속함.

훌륭한 객체지향 설계는 외부에 행동만을 제공하고 데이터는 행동 뒤로 감춰야 한다. 이 원칙을 캡슐화라고 부른다. 

객체를 디자인하기 위해서는 데이터가 아니라 행동을 먼저 생각해야 한다.

  1. 객체가 외부에 제공해야 하는 책임을 먼저 결정하고
  2. 그 책임을 수행하는 데 적합한 데이터를 결정한 후 (구현)
  3. 데이터를 책임을 수행하는 데 필요한 외부 인터페이스 뒤로 캡슐화해야 한다.

데이터를 먼저 결정하고 객체의 책임을 결정하는 방법은 유연하지 못한 설계라는 악몽을 초래한다.

https://medium.com/@limsungmook/자바스크립트는-왜-프로토타입을-선택했을까-997f985adb42

  • 너무도 좋은 글인데, 클래스와 프로토타입의 기원, 철학을 통해 this, hoisting에 대해 설명한다.

가볍게 정리하면

  • 클래스
    • 구체적으로 존재하는 사물이 있다면 반드시 그것의 본질이 존재한다는 것이 플라톤의 주장.
    • 추상적인 의자 = 클래스,
      실제로 존재하는 의자 = 인스턴스
    • 분류 이론과 일치 개체의 속성이 동일한 경우 개체 그룹이 같은 범주에 속한다. 범주는 정의와 구별의 합이다
  • 프로토타입
    • 분류 이론을 정면으로 반박
    • 의미사용이론 ⇒ 사용에 의해 의미가 결정된다. 즉, 단어의 진정한 본래의 의미란 존재하지 않고 상황과 맥락에 의해서 결정된다. 맥락(컨텍스트)가 중요.
    • 가족 유사성 ⇒ 현실에서 실제로 대상을 분류할 때 속성이 아닌 가족 유사성을 통해 분류. 공통된 특성이 없더라도 같은 분류가 가능하다.
    • 그렇다면 프로토타입이란?
      • 객체는 ‘정의’로부터 분류되는 것이 아니라 가장 좋은 보기(prototype, exemplar)로부터 범주화된다.
        • ex) 참새(원형), 타조
      • 같은 단어라 할지라도 누가 어떤 상황(context)에서 접했나에 따라 의미가 달라진다는 것입니다. (의미사용이론)
profile
개발이 즐겁고 노는게 즐거워요

0개의 댓글