티스토리 뷰

안녕하세요. 좋아요요정입니다.

자바스크립트는 ES6에서 클래스 기반 언어들과 비슷하게 객체를 생성하는 "클래스"가 추가되었습니다. 프로토타입 객체지향 프로그래밍의 자바스크립트에서는 기존 생성자함수를 이용해 객체를 생성했었는데 클래스가 추가된 이유는 무엇일까요? 그리고 클래스 사용법이 어떠한지 함께 살펴보겠습니다.

 


 

클래스

클래스는 객체를 생성하기 위한 탬플릿입니다. 붕어빵을 찍어내는 틀 = 클래스, 붕어빵 = 객체라는 예시가 유명합니다.

자바스크립트는 프로토타입 기반의 객체지향 패러다임을 제공하는 언어입니다. 클래스가 도입되기 이전의 자바스크립트는 프로토타입을 사용하기 때문에 클래스 키워드가 없이도 생성자함수, prototype을 이용해 객체지향에서 사용하는 상속기능을 구현할 수 있습니다.

자바스크립트의 방식이 기존 클래스기반 언어를 사용하던 사람들에게는 직관적이지 못하고, 기존 객체지향 프로그래밍 방식에서 혼란을 느끼곤 했다고 합니다. 그래서 자바스크립트는 ES6부터 클래스 키워드를 제공하고, 클래스가 도입된 이후로 클래스 기반으로 객체 인스턴스를 생성하고 상속 기능을 사용할 수 있게 해주었습니다.

 

그런데 자바스크립트에서의 클래스는 사실 "특별한 함수"입니다. 

자바스크립트 ES6의 클래스는 더 쉽게 생성자함수를 만들게 해주는 도우미 역할, Syntatic Sugar라고 표현하기도 합니다. 

왜 클래스가 "함수"인지, 이 도우미 역할이라는 것은 무슨 의미인지, 기존 생성자함수와 클래스가 어떤 차이가 있는지 살펴보겠습니다.

 

 


클래스 정의

클래스 선언식 정의

class Person {}

클래스를 정의하는 방법은 class 키워드를 이용하는 것입니다.

class와 클래스명을 입력해주고 코드블럭에 내부 class body를 작성해줍니다. 이때 클래스명은 보통 생성자 함수처럼 파스칼 케이스(첫글자를 대문자로)로 작성하는 것이 규약에 있지만 그렇지 않다고 오류가 발생하는 것은 아닙니다.

 

클래스 표현식 정의

let Person = class {}
const Person2 = class PersonClass {}

클래스를 정의할 때 표현식으로도 정의할 수 있습니다. 이 때 class는 무명클래스, 기명클래스로도 모두 정의할 수 있으나, 기명클래스의 식별자로 호출할 수 없습니다. 클래스 표현식으로 정의된 클래스 호출은 클래스가 할당된 변수명으로 호출하게 됩니다.

표현식으로 정의할 수 있는 것을 보면 클래스도 결국 일급 객체라는 것을 알 수 있습니다. 따라서 클래스는 리터럴로 생성이 가능하고, 함수의 반환값으로도 사용할 수 있는 등 일급객체와 같게 사용이 가능합니다.

 

생성된 클래스를 살펴보면 생성자함수에서 확인했던 prototype과 내부슬롯 [[prototype]]을 확인해볼 수 있습니다.

class도 생성될 때 prototype객체가 함께 생성되고 서로를 참조하고 있는 것을 확인해볼 수 있습니다. 그리고 class의 원형이 되는 프로토타입은 Function인 것을 확인됩니다. 이는 생성자함수를 생성할때와 같습니다.

 

 


클래스 구조

클래스는 클래스 중괄호 {}의 안쪽 부분에 해당하는 class body에 constructor, 프로토타입 메서드, 정적 메서드, get, set  등을 정의할 수 있습니다. 클래스 코드 내부 클래스 자체에서 변수나 this 바인딩을 통해 프로퍼티 등록하면 에러가 발생합니다.

 

constructor

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

const mom = new Person("엄마");
mom.name //"엄마"

constructor메서드는 class 안에서만 사용되는 특수한 메서드입니다.

클래스로 객체를 생성할 때 초기화에 등록될 매개변수를 받고, 객체 인스턴스를 생성해 초기화한 후 전달받은 인수 목록을 객체 인스턴스에 할당해주는 역할을 합니다. 이때의 this는 생성자함수의 this와 마찬가지로 생성되는 객체 인스턴스를 가리킵니다. 

 

객체의 prototype.constructor와는 다릅니다. prototype의 constructor 프로퍼티는 인스턴스의 프로토타입을 만든 원형이 되는 생성자함수를 참조하고, class의 constructor는 클래스가 new키워드와 함께 호출되었을 때 인스턴스를 만들어 초기화시켜주는 메서드입니다. constructor 메서드는 클래스 안에 한 개만 존재할 수 있습니다. 

 

 

 

프로토타입 메서드

class Person {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
    	console.log(`[${this.name}]: 안녕하세요!`);
    }
}

const mom = new Person("엄마");
mom.sayHi() // [엄마]: 안녕하세요!

프로토타입 메서드는 class 바디 내부에 메서드명(인수목록) { 메서드내용 }을 작성해줍니다.

해당 클래스로 생성된 객체 인스턴스가 사용할 수 있는 메서드이며, 이 때의 this는 메서드를 호출한 객체 인스턴스를 가리킵니다.

왜 프로토타입 메소드라고 불릴까요? 생성자함수에서는 따로 prototype에 메서드를 등록해 객체 인스턴스들이 사용할 수 있도록 구현했었습니다. 클래스에서는 클래스 내부에 예시의 sayHi()처럼 메서드를 등록하면 클래스로 생성된 객체 인스턴스들이 모두 사용할 수 있습니다. 동일한 기능을 구현해주는 것 뿐만 아니라, 생성자함수와 마찬가지로 class의 prototype에 sayHi()가 등록됩니다.

class의  prototype과 mom의 내부슬롯 [[prototype]]이 같은 prototype객체를 가리키고 있는 것을 확인할 수 있습니다. 

생성자함수처럼 따로 prototype에 메서드를 등록해 사용하는 것보다, 클래스 내부에 선언함으로써 가독성도 좋고, 굳이 prototype에 직접등록하는 코드를 작성하지 않아도 되어서 더욱 쉽게 느껴집니다.

 

 

정적(static) 메서드와 속성

class Person {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
    	console.log(`[${this.name}]: 안녕하세요!`);
    }
    static verson = 2022;
    static sayGoodnight() {
        console.log(`[공지]: 안녕히 주무세요!`);
    }
    
}

const mom = new Person("엄마");
mom.sayGoodnight() // Uncaught TypeError
Person.sayGoodnight() // [공지]: 안녕히 주무세요!
Person.verson // 2022

정적 메서드는 클래스가 사용하는, 클래스의 프로퍼티에 등록이 되는 프로퍼티입니다. (인스턴스는 호출할 수 없습니다.) 

일반 프로토타입 메서드와 다르게 선언시 static 키워드를 작성해서 클래스를 위한 정적 메서드, 속성임을 구분해 정의할 수 있습니다. 해당 메서드의 등록은 class의 프로퍼티로 직접 등록이 됩니다. 이때의 this는 클래스 자체를 가리킵니다.

 

 

 

 

getter, setter

객체의 접근자 프로퍼티인 getter, setter를 클래스에서도 정의가 가능합니다.

 일반 객체에서 접근자프로퍼티를 정의하고 사용하던 것처럼 사용할 수 있습니다.

class Person {
    constructor(name) {
        this._name = name;
    }
	get introduce() {
        console.log(`나는 ${this._name}입니다.`);
    }
    set name(name) {	
        this._name = name
    }
    
}

const mom = new Person("엄마");
mom.introduce // '나는 엄마입니다.'
mom.name = '할머니딸';
mom.introduce // '나는 할머니딸입니다.'

접근자 프로퍼티와 함수로 헷깔리는 것.. 함수를 호출하듯 () <- 를 사용하는 것이 아니라 데이터 프로퍼티에 접근하는 것처럼 사용하는 것이 헷깔립니다.

 

 

 

 

Field 선언

'public과  private 필드 선언은 자바스크립트 표준화 위원회에 실험적 기능 (stage 3)  TC39 로 제안되어있습니다.  현재 이를 지원하는 브라우져는 제한적인 상태입니다만, Babel 과 같은 build 시스템을 사용한다면 이 기능을 사용해볼 수 있습니다 '

 

 

Public 필드

클래스 필드는 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어입니다. Public과 Private필드로 나뉘며 상황이 맞게 사용할 수 있습니다. 

Public 필드는 일반적으로 사용하는 프로퍼티와 같습니다. 

사람 객체를 만들 때 살아있음 = true를 늘 추가해줘야 한다고 생각해봅시다. 매번 생성할 때마다 매개변수로 true를 추가해주지 않아도, 클래스의 Public 필드에 선언해두면 객체 인스턴스를 생성할 때 언제나 기본적으로 가지게 되어집니다.

 

class Person {
    isAlive = true;
    constructor(name) {
        this.name = name;
    }
    sayHi() {
    	console.log(`[${this.name}]: 안녕하세요!`);
    }
}

const mom = new Person("엄마");
mom.isAlive // true

 

 

Private 필드

Private 필드는 클래스 외부에서 임의로 수정할 수 없도록 하기 위해 사용됩니다.

장바구니의 결제금액을 수정한다거나, 게임의 캐쉬, 혹은 아이템수를 무작위로 늘리게 되면 원치 않는 방향으로 흘러갈 것입니다. 

클래스 내부에서만 읽고 쓰기가 가능하도록 Private 필드로 구현해줄 수 있습니다. 

엄마의 몸무게는 비밀로 만들어 보겠습니다.

class Person {
    _isAlive = true;
	#weight;
    constructor(name, weight) {
        this.name = name;
        this.#weight = weight;
    }
    sayHi() {
    	console.log(`[${this.name}]: 안녕하세요!`);
    }
}

const mom = new Person("엄마", '77kg');

엄마가 살아있는 것은 알 수 있지만 엄마의 몸무게는 확인할 수 없죠

Private 필드는 사용전에 선언되어야 하고, 일반적인 프로퍼티와 다르게 값을 할당하면서 만들어질 수 없습니다.

 

 


여기까지의 중간정리.

  역할 문법 호출 특이사항
constructor 객체 인스턴스 생성, 초기화 역할 constructor(인자) {
  this.프로퍼티 = 인자;
}
클래스로 객체 생성시  클래스에 오직 1개
프로토타입 메서드 객체 인스턴스가 사용할 수 있게 클래스의 prototype에 등록됨 메서드명() {} 객체 인스턴스가 호출 가능  
정적 메서드 클래스자체의 프로퍼티에 등록됨 static 메서드명() {}
statie 키 = 값;
클래스가 호출 가능 상속 안됨
getter, setter 객체 인스턴스의 접근자 프로퍼티. 클래스 내부에 선언되고 데이터 프로퍼티를 접근,조작 get 함수명() {}
set 함수명(인자목록) {}
객체인스턴스.프로퍼티  
Public 필드 객체 인스턴스 생성 시 기본적으로 갖게 될 프로퍼티 프로퍼티키 = 값; 객체인스턴스.프로퍼티  
Private 필드 클래스 내부에서만 읽기 쓰기 가능한 프로퍼티(getter,setter이용) #프로퍼티키 = 값; 외부에서 임의로 수정 불가 사용 전에 선언되어야 함

 

 

 


 

인스턴스 생성 과정 

const 변수명 = new 클래스명(인수목록);

클래스로 인스턴스를 생성할 때에는 new키워드와 함께 클래스명을 작성해 호출합니다.

위의 예시를 보면 const mom = new Person("엄마")를 확인하실 수 있습니다.  

new키워드와 함께 클래스가 호출되면 객체 인스턴스가 생성되고 this에 연결이 됩니다. 

constructor메서드 내에 정의된 프로퍼티를 할당되어지고, 인스턴스가 반환됩니다. 

 

 

 

 


extends를 통한 클래스 상속

자바스크립트의 생성자함수에서는 객체 생성자의 상속을 통해 자식 생성자함수를 만들 때, call()로 this 바인딩을 해 데이터프로퍼티를 등록하고, prototype에 메서드를 등록함으로써 자식 생성자함수도 사용할 수 있게 했었습니다.

 

클래스는 extends와 super 키워드를 통해 더 간결하게 상속기능을 사용할 수 있습니다.

extends키워드는 class를 선언할 때 [class 생성될클래스명 extends 부모클래스명 {} ]을 통해 하나의 부모 클래스에게 상속을 받아올 수 있습니다. 그리고 클래스 내부 바디에서 super 키워드를 이용해 부모 클래스를 호출할 수 있습니다. super를 통해 기존 프로토타입 기반 상속보다 더 쉽게 호출할 수 있습니다.

 

class Person {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        console.log(`[${this.name}]: 안녕하세요!`);
    }
}

class Developer extends Person {
    constuctor(name, language) {
        super(name)
        this.language = language;
    }
    coding() {
        console.log(`${this.name}은 ${this.language}를 사용해 코딩을 합니다.`);
    }
    sayHi() {
        super.sayHi();
        console.log(`[${this.name}]: 네 공부할게요!`);
    }
}

const jiu = new Developer('좋아요요정', '자바스크립트');
jiu.coding() //좋아요요정은 자바스크립트를 사용해 코딩을 합니다.
jiu.sayHi() //[좋아요요정]: 안녕하세요!
	//[좋아요요정]: 네 공부할게요!

 

super  키워드를 사용하면

이전 생성자함수의 오버라이딩(자식 생성자함수에서 동일한 메서드명이 있을 시 자식이 호출되고 부모생성자함수의 메서드가 섀도잉 되던 현상)현상으로 부모 생성자함수의 동일한 메서드명에 접근할 수 없었던 문제가 해결이 가능합니다.

동일한 메서드 명이어도, 메서드 내에서 super 키워드로 부모 클래스를 호출하게 되면, 부모 클래스의 메서드에 접근이 가능하기 때문입니다. 멋지네요

변수의 타입이나 유무를 확인해 부모메서드를 출력하거나, 자식 메서드를 출력하는 코드도 작성이 가능하게 됩니다.

 

 

 

마지막으로 클래스는 재정의될 수 없습니다. 클래스를 재정의를 시도하면 SyntaxError가 발생합니다.

 

 

 

자바스크립트에서 클래스는 "특별한 함수"이다.

자바스크립트에서의 클래스는 생성자함수와 마찬자기로 prototype기반의 상속기능을 유지하고, 객체 인스턴스들이 prototype에 등록된 메서드를 사용하고, Function을 원형 프로토타입으로 가지고 있는 것을 확인했습니다. 하지만 생성자함수와는 다르게 코드와 메서드를 더 쉽게 생성하고, 가독성이 좋게 메서드를 정리할 수 있고 한눈에 어떤 기능을 가지고 있는 메서드인지 파악을 할 수 있었습니다. 

또한 extents를 통해 직접적으로 부모클래스를 입력해 상속받아올 수 있으며, super 키워드로 부모클래스를 호출할 수 있었습니다. 

생성자함수와 같게 prototype기반의 상속기능을 구현하고 함수를 원형으로 하는 일급 객체인 클래스이지만, 클래스는 생성자함수의 역할만을 하는 것이 아니며 부모 클래스를 직접 지정할 수 있고, 내부에서 호출할 수 있으며, 코드를 더 용도에 맞게 구분이 가능하도록 키워드를 사용해 작성할 수 있기 때문에 강점이 더해졌다고 생각합니다.

"특별한 함수" 클래스에 대해서 알아보았습니다.

 


 

길고 미뤘던 클래스를 이렇게 마무리 짓습니다. 작성하면서 또 지식이 정리되게 되네요.. (스터디 블로그 숙제의 참 기능!)

어느덧 1월의 마지막날이 가까워지고 있습니다.

1월 한달동안 많은 일들이 있었고, 또 많이 배웠네요. 지난 기간동안 자바스크립트의 원리를 제대로 알지 못한 채 사용해왔네요.. 눈으로 보고 귀로 들은 정보가 다가 아니라, 이렇게 정리를 하면서 더 깊게 깨닫게 되는 것 같습니다. 1월동안 스터디를 진행하며 배운 내용들이 앞으로의 인생에 큰 도움이 될 것 같습니다. 진행해주시는 hoon님, 함께 스터디로 으샤으샤하는 러버덕스터디원분들 모두 감사합니다! 

늘 건강하시고 행복한 코딩라이프되세요~! 

 

 

JavaScript 스터디 팀 러버덕과 함께 합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함