[JS] Class란?

Sungho Kim·2022년 11월 17일
0

JavaScript

목록 보기
4/5

시작에 앞서,

ES6가 나오면서 javaScript의 function을 대체하는 기능들이 나왔다. 기본적으로 javaScript의 function은 3가지 역할을 하는데, constructor(생성자), regular function(함수), method(메서드)다.

이번 포스팅으로는 세가지 역할중 constructor(생성자)에 대해 알아보자

생성자 함수

ES6가 나오기 전에 자바스크립트에서 객체를 만드는 방법에 대해 알아보면

function Person(firstName, lastName){
	this.firstName = firstName;
  	this.lastName = lastName;
  	  this.fullName = function fullName() {
    console.log(this.lastName + this.firstName);
  };
}
const bob = new Person("bob","kim")
bob.fullName(); // kimbob

이런식으로 함수를 통해 객체를 만들게 되는데, 잘 보면 person이라는 함수 내에 return하는 값이 하나도 없는데도 불구하고 bob이라는 객체가 생성된다. 어떻게 이런일이 일어날 수 있는 걸까?

1. new 연산자가 새로운 빈 객체를 메모리 상에 생성함

const bob = new Person('bob')

2. 생성된 빈 객체가 this에 바인딩 됨

function Person(firstName, lastName){
	this.firstName = firstName;
  	this.lastName = lastName;
  	  this.fullName = function fullName() {
    console.log(this.lastName + this.firstName);
  };
}

3. this 객체의 속성을 채우는 동작이 수행됨

4. return하는게 없다면, 그렇게 만들어진 this가 return이 됨

Class함수

클래스란 간단하게 말해서 객체를 만들어내는 기계이다.

앞에서 생성자 함수의 원리에 대해 알아보았다. Javascript에서 함수의 기능이 3가지가 있다고 했는데, 아무때나 쓸수 있다는 것은 내가 1번의 목적으로 사용할때 필요가 없는 2,3번의 기능이 있어서 효율적이지 않다 라고 표현 될수도 있다.

또한, 협업을 할때 나는 1번 목적으로 사용을 했는데, 동료가 이게 1번 목적으로 사용되었는지, 2번목적으로 사용 되었는지, 3번 목적으로 사용 되었는지 파악을 하려면 볼때마다 의도 파악을 해야한다는 비효율적인 상황이 나온다.

ES6에선 생성자 함수 전용으로 사용 할 수 있는 class라는 개념이 도입되었는데, 우선 어떻게 사용되는지부터 알아보자.

사용법

class PersonClass {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  fullName() {
    console.log(this.lastName + this.firstName);
  }
}

const muji = new PersonClass("muji", "Dan");
const santa = new PersonClass("santa", "Clause");
muji.fullName();// danmuji
santa.fullName();//santaClause

class와 function의 차이

constructor function을 통해 정의한 메소드는 prototype에 들어가는게 아니라 constructor로 들어가는걸 알 수 있다.


function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = function fullName() {
    console.log(this.lastName + this.firstName);
  };
}

const bob = Person("bob", "kim");

class PersonClass {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  fullName() {
    console.log(this.lastName + this.firstName);
  }
}

const muji = PersonClass("muji", "Dan");

bob // undefined 
muji //Uncaught ReferenceError: muji is not defined at <anonymous>:1:1

또한 class와 constructor에 각각 new를 제외하고 출력을 해보면 constructor에서는 undefined로 에러를 발생하지 않지만, muji에서는 new가 에러메시지가 나오는걸 볼 수 있다.

개발을 처음 할때는 에러 메시지가 적으면 적을수록 좋았지만, 프로젝트 규모가 커질수록 에러를 초기단계에서 없애고 가는게 좋은데, ES6가 나오면서 부터는 각 목적에 맞게 함수를 알아서 선택해서 쓸 수 있고, 해당 기능에 충실해진 느낌이다.

class가 하는 일은 비슷한 객체를 가능한 편하고 빠르게 만드는데에 목적이 있으니, new 연산자를 통해 해당 기능을 충실히 수행하게 만드려는 목적으로 보인다.

Prototype

Prototype은 무엇일까? 쉽게 말하면 유전자라고 표현하면 좋을꺼 같다. 부모에서 자식에게 유전자를 전달할때 갈색 눈이 복사되서 자식에게 가는게 아니라, 갈색 눈처럼 보이는 유전자를 전달 받은것이고, 자식의 갈색 눈을 보면 부모님을 쉽게 찾을수 있는 쉽게 말해 "연결"이라는 키워드로 보면 된다.

앞서 class에서는 메소드가 class 객체에 prototype으로 상속된다고 표현을 했는데, 정확히는 복사가 아닌 연결이 된다고 이해하면 된다.

function sayHello() {
  console.log(`${this.name}: hello!`);
}

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

const sungho = new Person("sungho");
sungho.sayHello();
//Uncaught TypeError: sungho.sayHello is not a function

당연히 person이라는 클래스에 sayHello()라는 메소드가 없으니 에러메시지가 나온다. Person이란 객체에 prototype을 넣어보자

function sayHello() {
  console.log(`${this.name}: hello!`);
}

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

Person.prototype.sayHello();// 추가

const sungho = new Person("sungho");
sungho.sayHello();
//sungho: hello!

이렇게 잘 작동되는걸 볼 수 있다.

Prototye Chaining

위에 예를 보면 sungho라는 객체에 prototype을 한게 아니라 Person에 prototype을 했고, sungho에서 sayHello()라는 메소드를 상속 받았는데, 이럴때 나오는 개념이 prototype chaining이다.

더 확실한 이해를 위해 예제 하나를 더 보자

class Car {
  constructor(color) {
    this.color = color;
    this.wheel = 4;
  }
  drive() {
    console.log("go");
  }
  stop() {
    console.log("stop");
  }
}

class Kia extends Car {
  park() {
    console.log("park");
  }
}

const k5 = new Kia("white");
console.log(k5);
k5.drive(); //go

위의 class를 보면 Kia class에는 drive()라는 메소드가 없다는걸 볼 수 있다. 하지만 그 밑에 Car라는 클래스 안에는 drive()메소드가 있는게 보이는데, prototype chaining은 이처럼 해당 메소드가 해당 클래스에 존재하지 않더라도, 부모 클래스 프로토 타입에 존재하는지 찾은 후, 해당 메소드를 실행시키는걸 prototype chaining 라고 한다.

Overiding

기존에 존재하는 부모 class에 기존 method를 덮어 씌우는것 혹은 onstructor 추가하는걸 overiding이라고 한다. method를 추가할땐 부모 클래스에 있는걸 유지하고 싶을땐 super.메소드를 쓰면 되지만 constructor을 추가할땐 몇가지 유의해야할 사항이 있다.

class Car {
  constructor(color) {
    this.color = color;
    this.wheel = 4;
  }
  drive() {
    console.log("go");
  }
  stop() {
    console.log("stop");
  }
}

class Kia extends Car {
  // 이부분 추가
  constructor() {
    this.handle = 1;
  }
  park() {
    console.log("park");
  }
}

const k5 = new Kia("white");

console.log(k5);
k5.drive();

이렇게 Kia class에 새로운 Constructor을 추가할 경우, 아래의 에러가 뜬다.

prototype.js:16 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

여기에서 this.handle위에 super();를 넣어주면 에러가 없어지는데, 왜 이런 에러가 나는걸까??

그 이유는 생성자함수가 만들어질때, 빈 객체를 만들어서 this를 객체에 넣는 방법으로 객체를 생성하게 되는데, 위 예제처럼 extends를 써서 만든 자식 클래스는 빈객체를 만들고 this를 할당하는 절차를 건너 뛰게된다. 따라서 supser();을 통해 부모클래스의 객체를 할당해준 후에, 그 객체에 새로운 property를 만드는 순서로 작업을 진행해야 한다.

profile
공유하고 나누는걸 좋아하는 개발자

0개의 댓글