[ Javascript Class ]

carrotsman·2022년 4월 6일
5

프론트엔드

목록 보기
7/34
post-thumbnail

Class

클래스(class)는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀이다. 객체를 정의 하기 위한 상태(멤버변수)와 메서드(함수)로 구성된다.

class의 사전적 정의다. class는 객체지향프로그래밍(이하 OOP로 줄인다.)을 설명할때 빼놓을수 없는 컨텐츠 중 하나다. OOP에 있어 개발 프로세스는 다음과 같다.

  1. 설계도에 해당하는 class를 구현한다.
    // iPhone7의 설계도를 만든다.
    class iPhone7 { 
        ...
    }
  1. 해당 설계도(class)를 활용하여 새로운 객체(instance)를 생성한다.
    iPhone7 newPhone = new iPhone7();
  1. 생성된 객체에 데이터를 설정하고 비즈니스 로직을 수행한다.
    newPhone.setNumber("010-0000-0000");
    newPhone.setICloudId("jobs");
    ...
    
    newPhone.takePicture();
    ... 

다음과 같이 객체를 생성하고 그 객체마다 데이터 설정, 메소드 호출 등을 통해 비즈니스 로직을 수행하며 프로그램을 완성해나가는 것이 OOP 디자인 패턴이다.
javascript에서도 OOP 패턴으로 코딩이 가능한데 그것을 가능케 해주는 것이 ES6에서 추가된 class 문법이다.


Class 구성 요소

보통 class를 구성하는 컨텐츠는 3가지 정도로 분류할 수 잇다.

  1. 생성자 : new 키워드로 객체를 생성할때 호출되는 메소드, 객체를 생성할 때 파라미터로 전달하여 호출함.
  2. 데이터 변수 : 객체가 갖고있을 속성 값 (그냥 javascript짤때 변수 선언부라고 생각하면 된다.)
  3. 메소드 : 객체가 호출할 수 있는 메소드 (javascript로 따지면 function같은 것)

class에 대해서는 java 시리즈에서 다루도록 하겠다. 대충 이런식이라는 것만 알고 넘어가자.


ES5에서 Class 문법

기존에 ES6 이전에도 class 를 사용할 수 있었다. class 라는 예약어가 있는게 아니라 function을 class 형태로 선언해서 사용하는 문법이라고 생각하면 된다. class의 메소드(method)는 javascript에서는 함수(function)을 뜻한다. 예제 코드에선 편의상 메소드라 칭하겠다.

ES5에서는 class 전용 문법이 없기 때문에 prototype형태로 메소드를 선언해줘야한다. 이때 메소드는 2가지 형태를 띄는데 다음과 같다.

  1. static 메소드 : new 키워드로 객체를 생성하지 않고 class 네임스페이스로 호출할 수 있는 메소드
  2. prototype 메소드 : new 키워드로 생성된 객체를 네임스페이스로 호출할 수 있는 메소드

지금은 이렇게 2가지 종류의 메소드가 있다고 인지하고 넘어가자.
다음은 ES5에서 class 선언 코드로 내부에 변수 name 과 static 메소드와 prototype 메소드를 포함하고 있다.

    // ES5
    var class1 = function (name) {
        this.name = name;
    };
    class1.staticFunction = function () { // static 메소드
        console.log(this.name + ' : call static method!');
    };
    class1.prototype.basicfunction = function () { // 객체가 사용할 prototype 메소드
        console.log(this.name + ' : call prototype method!');
    };

    class1.staticFunction();
    // class1 : call static method!

    var instance1 = new class1('carrots');
    instance1.basicfunction();
    // carrots : call prototype method!

ES5에선 이런 방식으로 class 함수를 구현하여 사용할 수 있다.
class1 이라는 class 함수를 만들고 new 키워드로 객체를 생성해 instance1 에 할당했다.

위 코드를 보면 static 메소드는 객체 생성없이 class1 를 통해 호출했고,
prototype 메소드는 instance1 를 통해 호출한 것을 확인할 수 있다.
이렇게 메소드 선언 형태는 2가지로 분류될 수 있는 것이다. 그 class가 가진 고유의 메소드인지, 생성된 객체가 가질 메소드인지의 차이다.

static 메소드와 prototype 메소드는 this 가 참조하는 대상도 다른데
다음 결과를 보면 둘다 같은 this.name 을 사용했는데 static 메소드는 class1 을 참조했고, prototype 메소드는 생성시 파라미터로 전달했던 carrots 을 참조하여 출력한 것을 확인할 수 있다.

  • static 메소드의 경우 class1 자체를 참조해서 출력했다. class1.staticFunction 내부에서의 thisclass1 함수 껍데기를 지칭하는 것이다. 함수 객체는 내부적으로 name 이라는 속성이 존재한다. 그래서 해당 class name이 참조된 것이다.
  • prototype 메소드의 경우 instance1 은 생성된 오브젝트기 떄문에 내부에서 사용된 thisinstance1 를 지칭한다. 그렇기 때문에 instance1 내부에 갖고있는 name 변수를 참조해 출력한 것이다.

console.log 로 두녀석을 찍어보면 다음과 같다.

    // ES5
    console.log(instance1);
    // class1 {name: 'carrots'}

    console.log(class1);
    /* ƒ (name) {
        this.name = name;
        this.aa = '';
    } */

뭐 무튼 static 메소드와 prototype 메소드의 차이는 객체를 생성 여부에 있다.


ES6에서 Class 문법

ES5에서는 저렇게 특정 기능을 제공해주지않아 선언하는데 불편함이 있었다. 근데 ES6 스펙에서 본격적으로 class 라는 키워드를 만들어줬다. 훨씬 가시적이며 명확해진 코드를 보자

    // ES6
    const class2 = class {
        constructor (name) {
            this.name = name;
        }
        static staticFunction () {
     	   console.log(`${this.name} : call static method!`);
    	}
    	basicfunction () {
        	console.log(`${this.name} : call prototype method!`);
    	}
    };

    class2.staticFunction();
    // class1 : call static method!

    var instance2 = new class2('carrots');
    instance2.basicfunction();
    // carrots : call prototype method!

와우 정말 간결해지고 가시적으로 class2 를 통해 사용할 수 있는 메소드라는 걸 알수있다. 기존에 javascript prototype으로 선언해줘야 했던 prototype 메소드는 메소드만 선언해서 사용할 수 있게 되었다. 여기서 알아두면 좋은 것은 ES6의 class 문법도 내부적으로는 javascript prototype을 사용하여 메소드를 할당한다는 것이다. 본질적으로는 같은 기능이다~ 이말이다. 에

뭐 대충 두 녀석의 내용을 정리하자면 이렇다.

구분ES5ES6
생성자function () {} 형태를 띈 구조체를 만들고
new 키워드를 사용하여 객체를 생성할때
함수로써 동작한다.
class 키워드로 선언된 본문 내부에
constructor 키워드로 생성할때
실행할 코드를 작성한다.
static 메소드class 함수명.static메소드 형태로 선언하여
객체 생성없이 사용한다.
class 구조체 내에 메소드명 앞에 static 키워드를 선언하여
static 메소드처럼 사용한다.
prototype 메소드class함수명.prototype.메소드명으로 선언하여
정의하고 new로 생성된 객체를 통해 호출한다.
class 구조체 내에 메소드를 선언하여 사용한다.

편해졌다 이거다.


Class 상속

class 구조체는 상속이라는 개념이 있다. 부모 (상위 class)와 자식 (하위 class) 관계가 존재하며, 자식 class는 상속받은 부모 class의 변수나 메소드를 참조하여 사용할 수 있다. 상속을 사용하는 이유는 이미 구현한 class의 메소드를 재사용해서 만들 수 있기 때문에 효율적이다. 예를 들면 이런거다.

내가 혐오하는 샘숭폰이 있다. 우리는 매년 다른 버전의 혐폰 발표를 본다. 👻 (샘숭폰 유저라면 미안하다.)
Galaxy1, Galaxy2, Galaxy3... 뭐 이런식으로 증식하는데, 이때 우리가 상속이라는 개념을 이해하기 위해 샘숭폰이 가진 원초적인 기능들에 대해 생각해볼 필요가 있다.
샘숭 폰이 버전이 계속올라가도 포함하고 있는 기능이 뭐가 있을까?

  • 전화 걸기
  • 문자 보내기
  • 구글 플레이스토어 기능
  • 사진찍기
  • 연락처 기능

등등등 ...
뭐 이런식으로 되있지 않겠는가? 이걸 매 버전마다 계속 다시 구현하진 않을 것이다. 미리 부모 class에 공통기능을 넣어놓고 Galaxy1, Galaxy2 이런 자식 class를 만들때마다 저 부모 class의 전화 걸기, 문자 보내기등의 메소드를 호출해서 사용한다. 같은 Galaxy 핸드폰이지만 버저닝이라던지 종류가 달라 class의 기능 확장이 필요할때 사용되는 것이 바로 상속스다.
한마디로 Galaxy1, Galaxy2, Galaxy3... 전부다 Galaxy지 않은가? 결론은 그말이다.

javascript class 문법에서도 다음처럼 상속의 형태를 사용할 수 있다. 참조 예시 ES5에선 진짜 이거만한 똥꼬쇼가 없는거 같은데 작성자분도 대단하다. 나도 이해를 위해 쉐도우 코딩을 했다. 헉헉..

	// ES5
    // 이 코드가 뭔가 하니 ES5에서는 상속을 지원하지않으니 그냥 javascript로 상속 기능을 구현한 것이다.
    /**
    * superClass : 부모 class
    * subClass : 자식 class (ES5니까 function ())
    * subMethods : 자식 class에서 확장할 메소드 목록
    */
	var extendClass = function (superClass, subClass, subMethods) {
    	// 부모 class의 prototype을 상속
        subClass.prototype = Object.create(superClass.prototype);
        
        // 자식 class에 생성자 함수 생성
        subClass.prototype.constructor = subClass;
      
        // 상속된 자식 class에서 사용하는 
        // super 키워드는 부모 클래스를 지칭하는 예약어다. 이걸 그냥 만들어버리네..
        subClass.prototype.super = function () {
        	// return 하는 함수 내부에서 this의 주체가 바뀌기 때문에 self 변수에 할당
            var self = this; 

            return function () {
                superClass.apply(self, arguments);
            }
        };
      
        // 확장할 메소드들이 있다면 그 확장기능을 prototype 메소드로 선언
        if (subMethods) {
            for (var method in subMethods) {
                subClass.prototype[method] = subMethods[method];
            }
        }
      
        // subClass의 prototype을 변경할 수 없게 freeze
        Object.freeze(subClass.prototype); 
        return subClass;
    };

    // 부모 class를 생성
    var Galaxy = function (number, message) {
        this.number = number;
        this.message = message;
    };
    Galaxy.prototype.phoneCall = function () {
        console.log('call to ' + this.number);
    };
    
    // 부모 class 상속받은 Galaxy1을 생성
    var Galaxy1 = extendClass(
        Galaxy, // 부모 class
        function (number, message) { // 자식 class
            this.super()(number, message);
        }, { // 확장할 메소드 object 타입으로 전달
            sendMessage : function () {
                console.log(this.number + ' : ' + this.message);
            }
        }
    );

    var newGalaxy1 = new Galaxy1('010-0000-1111', 'tq');
	newGalaxy1.sendMessage();
	newGalaxy1.phoneCall();
    // 010-0000-1111 : tq
    // call to 010-0000-1111

작성자에게 속았다. 그대로 하면 안된다. 칙쇼... 와타시노 시켄이...
그래서 잘되게끔 처리했당. ㅎㅎ 뭐 대충 내용을 보면 ES5에선 상속 키워드가 따로 없으니 만들어서 쓰는 내용이다. 아무튼 ES5에서 쓰려면 이런 불필요한 작업이 늘어난다.

다음은 ES6를 보자

    // ES6
    const Galaxy = class {
        constructor (number, message) {
            this.number = number;
            this.message = message;
        }
        phoneCall () {
            console.log(`call to ${this.number}`);
        }
    };

    const Galaxy1 = class extends Galaxy {
        constructor (number, message) {
            super(number, message);
        }
        sendMessage () {
            console.log(`${this.number} : ${this.message}`);
        }
    };

    const newGalaxy1 = new Galaxy1('010-0000-1111', 'tq'); 
    newGalaxy1.sendMessage();
	newGalaxy1.phoneCall();
    // 010-0000-1111 : tq
    // call to 010-0000-1111

엄청 간결해졌다. 이와 같이 ES6에 들어서면서 class 사용법도 굉장히 간편해졌다.


정리하며

javascript의 class, ES5, ES6 이후 바뀐 class 문법에 대해 알아봤다.
그냥 신규 스펙나오면 그거써라 겁나 편하니깐

오늘 저녁은 왕돈가스다. 🥕


참조 : https://typeof-undefined.tistory.com/7

profile
당근먹고 강력한 개발

0개의 댓글