JS에서 프로토타입과 클래스의 개념

BeomDev·2023년 4월 19일
0
post-custom-banner

생성자

생성자란 인스턴스를 생성하기 위해 new 연산자와 함께 사용되는 함수

생성자를 사용하는 이유

생성자를 통해 생성된 객체(인스턴스)는 같은 프로퍼티와 메서드를 공유할 수 있음

생성자 만들기

생성자는 기본적으로 함수이므로 함수가 필요함
생성자 함수는 관습적으로 첫글자에 대문자를 사용

function Func(){}
let inst = new func()

생성자를 사용해 인스턴스를 생성하기

// 생성자 함수
function Factory(name) {
  this.name = name;
  this.sayName = function() {
    console.log(`제 이름은 ${this.name}입니다.`)
  }
}

 // 인스턴스 생성
const robot = new Factory("도라에몽")

robot.sayName()
// 제 이름은 도라에몽입니다.

❗ 원래 함수에서의 this는 함수를 호출한 객체를 참조하지만, new 연산자를 사용하면 인스턴스를 참조한다

프로토타입

생성자를 사용해 인스턴스를 생산해낼 수 있지만, 메서드를 포함한 인스턴스를 생성하면 메서드가 인스턴스 수만큼 계속해서 생성된다

생성자를 이용해 인스턴스 생성

function Factory(name) {
  this.name = name;
  this.sayName = function() {
    console.log(`제 이름은 ${this.name}입니다.`)
  }
}
const robot = new Factory("도라에몽")
const robot1 = new Factory("짱구")
/*
.
.
99개의 robot 인스턴스 생성
.
.
*/
const robot100 = new Factory("100번째 로봇")
robot1.sayName === robot100.sayName // false

위의 예제에서 함수를 비교해 false가 나온것처럼 두 함수는 서로 다른 메모리 공간을 가르키고 있고, 100개의 인스턴스가 생성되면 그에 따라 100개의 함수가 생성된 것이다.

이런 자원의 낭비의 문제점을 보완하기 위해 프로토타입을 사용한다

프로토타입 : 모든 객체에서 해당 속성과 메서드를 사용할 수 있게 만들 수 있음

Factory에 프로토타입으로 sayName이라는 메서드를 추가

function Factory(name) {
  this.name = name;
}
// sayName이라는 메서드를 추가
Factory.prototype.sayName = function() {
  console.log(`제 이름은 ${this.name}입니다.`)
}
const robot = new Factory("도라에몽")
const robot1 = new Factory("짱구")
/*
.
.
99개의 robot 인스턴스 생성
.
.
*/
const robot100 = new Factory("100번째 로봇")
robot1.sayName === robot100.sayName // true
// 서로 같은 메모리 공간을 가르키고 있음

프로토타입을 사용해 모든 인스턴스가 하나의 메서드가 공유하도록 만들어 자원을 더 효율적으로 사용할 수 있게 해준다

prototype, __proto__

함수가 생성될 때 함수는 prototype을 가지게 된다.
인스턴스의 __proto__는 함수의 prototype과 서로 같은 데이터 공간을 가르키게 된다.

예시사진

객체의 상속

아래 예제는 생성자 함수에서 상속을 다룬 예제이다

Object.create

지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듬

function Parent() {
    this.name = 'Choi';
}
Parent.prototype.rename = function (name) {
    this.name = name;
}
Parent.prototype.sayName = function () {
    console.log(this.name);
}

const human = new Parent()
human.rename("BK")
human.sayName() // "BK"

// 자식 역할을 할 생성자 함수 생성
function Child() {
 Parent.call(this); 
 //	this로 사용할 객체가 Parent의 this
 // 새로 생성된 인스턴스의 this는 Parent를 가르킴 
 // Parent의 this를 참조하게 되는 것
}

// 대입연산자로 Parent의 프로토타입을 Child의 프로토타입에 할당
Child.prototype = Object.create(Parent.prototype);

const person = new Child()

call() 메서드와 prototype 할당으로 Child 객체는 Parent 객체의 모든것을 상속받게 된다

class

객체를 생성하기 위한 템플릿.
class 표현식과 class 선언문 두가지 방법을 제공
ES6 버전부터 추가되었으며 class 문법을 상속을 더 쉽게 구현할 수 있다.

class 기본 문법

class Basic{
  constructor(){
    
  }
  // 여러 메서드를 정의할 수 있다
  // 정의한 메서드는 Basic.prototype에 저장
  메서드() {
    
  }

생성자 함수 → class

생성자 함수를 class로 간단하게 표현할 수 있다

// 생성자 함수와 프로토타입
function Test(name){
  this.name = name;
}
Test.prototype.sayName = function(){
  console.log(`이름은 ${this.name}입니다.`)
}

const human = new Test('CHOI');
///////////////////////////////////////////

// class 문법
class test {
  constructor(name){
    this.name = name;
  }
  SayName() {
	console.log(`이름은 ${this.name}입니다.`)
  }
}

const person = new test('CHOI');

위 예시의 생성자함수와 class는 완전히 동일한 역할을 한다

클래스 문법에서의 상속

extends

extends 키워드는 클래스의 상속을 위해 사용된다.
상속은 이미 존재하는 클래스(부모)의 모든것을 파생 클래스(자식)에게 모든 속성과 메서드를 사용할 수 있게 해주는 개념

super

super 키워드를 통해 상속

super 키워드를 통해 상위 클래스의 생성자를 호출하거나, 메서드를 호출 할 수 있다.

생성자(constructor)에서는 super 키워드 하나만 사용되거나, this 키워드가 사용되기 전에 호출되어야 한다. (런타임 오류:예외 발생)

class Human {
  constructor(name) {
    this.name = name
  }
  
  sayName() {
    console.log(`안녕하세요 ${this.name} 입니다.`)
  }
}

class Me extends Human {
  constructor(name, age){
  	super(name);
    this.age = age
  }
  
  sayName(){
    super.sayName();
    console.log(`${this.age}살 입니다.`)
  }
}

const me = new Me('bk', '23')
me.sayName()
// 안녕하세요 bk 입니다.
// 23살 입니다.

getter와 setter

getter와 setter를 사용하면, 객체의 프로퍼티를 보호하고 유효성 검사도 수행할 수 있다.
get 구문은 객체의 속성 접근 시 호출할 함수를 바인딩한다

setter : 속성 값을 설정하는 메서드, set 키워드를 사용해 정의
set 구문은 객체의 속성에 할당을 시도할 때 호출할 함수를 바인딩한다

class에서 get과 set 키워드 없이 객체의 프로퍼티에 접근할 수 있으나, 직접 접근하는 것은 객체의 속성을 보호하지 못한다.
다른 코드에서 객체의 속성을 변경할 수 있으므로, 객체의 상태를 일관성 있게 유지하기 어렵다.

그래서 getter와 setter를 정의해두고, 객체의 프로퍼티에 간접적으로 접근해야 한다.
또한 getter와 setter를 사용하면, 객체의 프로퍼티를 보호하고 유효성 검사도 수행할 수 있다.

class Person {
  constructor(firstName, lastName) {
    this._firstName = firstName;
    this.lastName = lastName;
  }

  get firstName() {
    return this._firstName;
  }

  set firstName(value) {
    if (typeof value !== 'string') {
      throw new TypeError('firstName은 문자여야 합니다');
    }
    this._firstName = value;
  }
}

let Human = new Person("BK", "CHOI")
Human.firstName // getter로 간접적 접근
Human.firstName = 1 // setter로 간접적 접근

비공개 프로퍼티

ES6에서는 클래스 내부에서 비공개 프로퍼티를 만들 수 있도록 # 기호를 사용하는 프라이빗 필드(private field) 기능이 도입되었다.

class Circle {
  #radius = 0; // 프라이빗 필드
  
  constructor(radius) {
    this.#radius = radius;
  }
  
  getArea() {
    return Math.PI * this.#radius * this.#radius;
  }
}

let test = new Circle(30)
test.#radius = 100 // node.js 환경에서는 예외를 반환

getter,setter vs 비공개 프로퍼티

getter, setter와 비공개 프로퍼티는 기본적으로 같은 목적을 가지고 있다.

getter와 setter는 클래스 외부에서 접근 가능한 프로퍼티를 캡슐화하기 위해 사용되며 클래스의 프로퍼티에 직접적으로 접근하는 게 아니라 getter와 setter를 통해 접근하고 간접적으로 접근해 조작하기 때문에 클래스의 상태를 보호하고 유지할 수 있다.

반면, 비공개 프로퍼티는 클래스 내부에서만 접근 가능한 프로퍼티를 의미하는데, 클래스 외부에서는 비공개 프로퍼티에 직접 접근할 수가 없으므로 클래스 외부에서 프로퍼티의 값을 읽거나 변경할 수 없음, 즉 클래스의 상태를 완전히 보호할 수 있지만 클래스 내부에서만 사용할 수 있기 때문에 클래스 외부에서 필요한 값을 제공하기 위해 getter와 setter를 사용해야 할 수 있음

static

Class 문법에서 static 키워드를 사용하여 정적 메소드(static method)와 정적 속성(static property)을 정의할 수 있다.
이는 인스턴스를 생성하지 않고도 사용할 수 있는 속성과 메서드이다.

오버라이드

상위 클래스에서 정의된 메서드를 파생 클래스에서 재정의하여 사용하는 것을 의미한다.
하위 클래스에서 상위 클래스에서 정의된 메소드를 사용할 수 없거나, 새로운 동작을 구현해야 할 때 오버라이드한다.

파생 클래스에서 상위 클래스의 메서드를 오버라이드 하는 예제

class Say {
  sayHello(){
    console.log("부모 클래스 메서드")
  }
}

class AnotherSay extends Say {
  sayHello(){
	console.log("오버라이드")
  }
}

new AnotherSay().sayHello() // "오버라이드"
post-custom-banner

0개의 댓글