ES6 Class #1

February 15, 2016

1. 글에 대해

  • 이 글은 Hika Maeng 님이 추천해주신 ECMAScript 6 길들이기라는 책과 개인적인 테스트를 통해 작성되었습니다.

  • 또한 모든 결과는 Chrome 브라우저를 통해 테스트된 결과입니다.

2. 클래스 선언 및 표현식

  • ES6 클래스는 ES5 가 가진 객체 지향 모델을 좀더 명시적으로 다룰수 있도록 개선된 새로운 모델이다.

  • 클래스 선언식

    // 클래스 선언
    class A {
        constructor(id){
    
            console.log(arguments); // ['yanione']
    
            try{
                console.log(arguments.callee);
            }
            catch(e){
    
                // ES6 클래스 body 는 기본적으로 strict mode 위에서 동작한다.
    
                // 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions
                // or the arguments objects for calls to them
                console.log(e.message);
            }
    
            this.id = id;
        }
    
        // 프로토타입 맴버 선언
        getId(){
            return this.id;
        }
    }
    
    // 선언된 클래스는 기존 함수 객체와 거의 유사한 구조를 가지고 있다.
    console.dir(A);
    
    // 선언된 클래스는 function 데이터 타입을 반환한다.
    console.log(typeof A); // function
    
    // 선언된 클래스의 constructor 속성은 Function (생성자)함수 객체를 반환한다.
    console.dir(A.constructor); // Function function object
    
    // 선언된 클래스가 가진 원형 객체
    console.log(A.prototype);
    
    // 선언된 클래스의 prototype 객체 프로퍼티는 getId 맴버를 포함하고 있다.
    console.log(A.prototype.getId); // getId function object
    
    // newAObject 객체를 생성한다.
    var newAObject = new A('yanione');
    
    // newAObject 객체의 __proto__ (내부)속성은, A.prototype (원형)객체를 참조하고있다.
    console.log(newAObject.__proto__ === A.prototype); // true
    
    // newAObject 객체의 prototype chain 에는 A.prototype 객체가 존재하므로 아래 코드를 통해 true 를 반환하게 된다.
    console.log(newAObject instanceof A); // true
    
    // newAObject 객체의 (생성자)함수(클래스)인 A 함수(클래스) 객체를 반환한다.
    console.log(newAObject.constructor); // A class
    console.log(typeof newAObject.constructor); // function
    
    console.log(newAObject.getId()); // yanione
    
    // newAObject 객체는, ES5 의 (생성자)함수 객체를 통해 생성된 객체와 동일한 구조를 가지고있다.
    console.dir(newAObject);

    선언된 클래스 내부

    class 0 1

    newAObject 객체 내부

    class 05

    정리

    • A 클래스는 기존 함수 객체와 거의 유사한 구조를 가지고 있다.

    • A 클래스는 function 데이터 타입을 반환한다.

    • A 클래스의 prototype 객체 프로퍼티는 선언된 프로토타입 맴버인 getId 메서드를 포함하고 있다.

    • newAObject.constructor 속성은 (생성자)함수인 A 함수(클래스) 객체를 참조하고 있다.

    • newAObject 객체는, ES5 의 (생성자)함수 객체를 통해 생성된 객체와 동일한 구조를 가지고있다.



  • 그밖의 테스트

    • 클래스 선언식EC 진입실행 코드 처리 후에도 VO 의 새로운 속성으로 추가되지않는다.

      // ec 진입 시 VO 의 새로운 속성으로 추가되지않는다.
      try {
          console.log(A);
      }
      catch(e){
          console.log(e.message); // A is not defined
      }
      
      // 클래스 선언식
      class A {
          ...
      }
      
      // 실행 코드 처리후에도 VO 의 새로운 속성으로 추가되지않는다.
      console.log(window.A); // undefined
    • 일반적인 함수 호출의 경우 아래와 같은 예외가 발생하게된다.

      // 클래스 선언
      class A {
          constructor(){
              console.dir(this);
          }
      }
      
      // 일반적인 함수 호출의 경우 아래와 같은 예외가 발생하게된다.
      
      try {
          A();
      }
      catch(e){
          // Class constructors cannot be invoked without 'new'
          console.log(e.message);
      }
      
      
      // call, apply, bind 메서드를 통해 호출한 경우에도 동일한 에러가 발생하게된다.
      try {
          A.call({});
      }
      catch(e){
          // Class constructors cannot be invoked without 'new'
          console.log(e.message);
      }
      
      try {
          A.apply({});
      }
      catch(e){
          // Class constructors cannot be invoked without 'new'
          console.log(e.message);
      }
      
      try {
          var _A = A.bind({});
          _A();
      }
      catch(e){
          // Class constructors cannot be invoked without 'new'
          console.log(e.message);
      }
      
    • ES5 에서 ES6 클래스와 같이, 일반적인 함수 호출막는 방법은 아래와 같다.

      function A(id, name){
      
          // this 값 내부 prototype chain 에 A.prototype 이 존재하지 않는 경우
          if (!(this instanceof arguments.callee)){
              throw new Error('Uncaught TypeError: Class constructors cannot be invoked without \'new\'');
          }
      
          // 인스턴스 맴버를 정의한다.
          this.id = id;
          this.name = name;
      
          return this;
      }
      
      // 일반적인 함수 호출의 경우, 초기화된 this 값(global Object) 내부 prototype chain 에는 A.prototype 이 존재하지 않는다.(즉 예외가 발생하게된다)
      A(); // Uncaught TypeError: Class constructors cannot be invoked without new\
      
      
      // 하지만 call(or apply, bind) 메서드를 통해, A.prototype 이 포함한 객체를 전달할 경우, (new 연산자가 생략된)일반적인 함수 호출을 막을수는 없다.
      console.log(A.call(Object.create(A.prototype, {age: {value: 18}}), 'yanione', 'mohwa')); // Object {id: "yanione", name: "mohwa", age: 18}
      
    • 선언된 클래스와 동일한 식별자 이름으로 선언 시 아래와 같은 에러가 발생한다.

      
      // 클래스 선언식
      class A {
          ...
      }
      
      // 선언된 클래스와 동일한 식별자 이름으로 선언 시 아래와 같은 에러가 발생한다.
      
      // Uncaught SyntaxError: Identifier 'A' has already been declared
      // var A;
      
  • 클래스 표현식

    // 클래스 표현식
    var A = class A{
        constructor(id){
    
            console.log(arguments); // ['yanione']
    
            try{
                console.log(arguments.callee);
            }
            catch(e){
    
                // ES6 클래스는 기본적으로 strict mode 위에서 동작한다.
    
                // 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions
                // or the arguments objects for calls to them
                console.log(e.message);
            }
    
            this.id = id;
        }
    
        // 프로토타입 맴버 선언
        getId(){
            return this.id;
        }
    }
    
    // 표현식에서 클래스명은 생략 가능하다.
    var _A = class{
        constructor(id){
    
            console.log(arguments); // ['yanione']
    
            try{
                console.log(arguments.callee);
            }
            catch(e){
    
                // ES6 클래스는 기본적으로 strict mode 위에서 동작한다.
    
                // 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions
                // or the arguments objects for calls to them
                console.log(e.message);
            }
    
            this.id = id;
        }
    
        // 프로토타입 맴버 선언
        getId(){
            return this.id;
        }
    }     
    
    // 선언된 클래스는 기존 함수 객체와 거의 유사한 구조를 가지고 있다.
    console.dir(A);
    
    // 일반적인 함수 호출의 경우 함수 선언식과 동일한 예외가 발생하게된다.
    try {
        A();
    }
    catch(e){
        // Class constructors cannot be invoked without 'new'
        console.log(e.message);
    }
    
    var newAObject = new A('yanione');
    console.dir(newAObject);
    
    var new_AObject = new _A('yanione');
    console.dir(new_AObject);

    선언된 클래스 내부(이전 클래스 선언식과 달리 <function scope> 내부에 Script 속성이 포함되지 않았다)

    class 0 2

    (기명) 클래스 표현식 결과

    var newAObject = new A('yanione');
    console.dir(newAObject);

    class 14

    (익명) 클래스 표현식 결과

    var new_AObject = new _A('yanione');
    console.dir(new_AObject);

    class 15

    정리

    • 클래스 표현식은 클래스명에 대한 기명 / 익명 표현이 가능하다.



  • 그밖의 테스트

    • 클래스 표현식EC 진입VO 의 새로운 속성으로 추가되지않는다.

      // ec 진입 시 VO 의 새로운 속성으로 추가되지않는다.
      try {
        console.log(A);
      }
      catch(e){
        console.log(e.message); // A is not defined
      }
      
      
      // 표현식에서 클래스명은 생략 가능하다.
      var _A = class A{
        ...
      }
      
      // 실행 코드 처리 후
      console.dir(window.A); // undefined
      
      // 선언된 _A 변수에 할당된다.
      console.log(window._A); // A class
    • 선언된 클래스를 (new 연산자를 생략 후) 호출 하면 에러가 발생한다.

      try{
          // 함수 호출
          A();
      }
      catch(e){
          // Class constructors cannot be invoked without 'new'
          console.log(e.message);
      }
      

3. 접근자 선언

  • 접근자 선언

    // 클래스 선언
    class A {
    
        constructor(id){
            // 접근자 사용을 위해 내부 속성인 __id__ 속성을 선언한다.
            this.__id__ = id;
        }
    
    
        // 접근자 생성 시 A.prototype 객체 내부에는 id 속성과 get/set 접근자 메서드가 생성되어있다.
        get id(){
            return this.__id__;
        }
    
        set id(value){
            value = value || '';
            this.__id__ = value;
        }
    }
    
    console.dir(A); // A class
    
    var newAObject = new A('yanione');
    
    console.dir(newAObject);
    
    // get
    console.log(newAObject.id); // yanione
    
    // set
    newAObject.id = 'yanione2';
    
    console.log(newAObject.id); // yanione2
    

    접근자 생성시 A.prototype 객체 내부에는 id 속성과 get/set 접근자 메서드가 생성되어있다.

    console.dir(A);

    class 06

    newAObject 객체 내부(newAObject 객체의 인스턴스 맴버id 속성이 추가된것을 볼 수 있다)

    var newAObject = new A('yanione');
    console.dir(newAObject);

    class 07

  • ES5 인스턴스 맴버를 통한 접근자 선언

    function A(__id__) {
                
        // 객체(this) prototype chain 에 A.prototype 이 존재하는지 않는 경우         
        if (!(this instanceof A)) return;
    
        var id = __id__;
    
        // ES5 에서는 오직 Object.defineProperty 메서드를 통해서만 접근자를 생성할 수 있다.
        Object.defineProperty(this, 'id', {
            get: function() {
                return id;
            },
            set: function(value) {
                id = value;
            }
        });
    }
    
    var new_AObject = new A('yanione');
    
    console.log(new_AObject);
    
    console.log(new_AObject.id); // yanione
    
    new_AObject.id = 'yanione2';
    console.log(new_AObject.id); // yanione2
    

    newAObject 객체 내부(이 경우 선언된 접근자 프로퍼티에 대한, 모든 접근자 메서드들이 인스턴스 맴버로 포함되는 단점(메모리)이 존재한다)

    • new 연산자를 통해 생성되는, 모든 객체에 선언된 모든 접근자 메서드가 생성되며, 그로인해 메모리가 낭비되는 단점이 존재한다.

      // new 연산자를 통해 생성되는 모든 객체(인스턴스)에 선언된, 접근자 메서드가 생성되며, 그로인해 메모리가 낭비되는 단점이 존재한다.
      var newAObject = new A('yanione');
      console.dir(newAObject);
      
      var newAObject2 = new A('yanione');
      console.dir(newAObject2);
      

      class 09



  • ES5 프로토타입 맴버를 통한 접근자 선언

    // A 함수 객체를 선언한다.
    function A() {
        if (!(this instanceof A)) return;
    }
    
    // A.prototype 객체에 id 속성에 대한 get/set 접근자 메서드를 생성한다.
    Object.defineProperty(A.prototype, 'id',{
        get: function(){
            // 내부 속성인 __id__ 속성을 통해 접근자에 접근한다.
            return this.__id__
        },
        set: function(value){
            this.__id__ = value;
        }
    });
    
    
    var newAObject = new A();
    
    console.dir(newAObject);
    
    newAObject.id = 'yanione';
    console.log(newAObject.id); // yanione
    

    Object.defineProperty 메서드를 통한, 접근자 생성시 A.prototype 객체 내부에는 id 속성과 get/set 접근자 메서드가 생성되어있다.

    console.dir(A);

    class 0 7

    newAObject 객체 내부(이 경우 ES6 에서의 접근자 생성과 같이, 선언된 모든 접근자 메서드들이 A.prototype 객체에 할당되며, 이전에 가졌던 (메모리 낭비에 대한)단점을 피할 수 있다)

    
    var newAObject = new A();
    console.dir(newAObject);

    class 0 6

4. 정적 메서드 선언

  • 정적 메서드 선언

    // 클래스 선언
    class A {
    
        constructor(){
        }
    
        // 정적 메서드 선언
        static post(url){
            this.url = url;
        }
    }
    
    console.dir(A);
    
    // A.post 메서드는 function 데이터 타입을 반환한다.
    console.log(typeof A.post); // function
    
    // 선언된 정적 메서드는 해당 클래스의 새로운 속성으로 할당된다.
    console.dir(A.post); // post function object
    
    // A.post.constructor 속성은 Function (생성자)함수 객체를 참조하고있다.
    console.log(A.post.constructor); // Function function object
    
    // A.post 정적 메서드는 prototype 객체 프로퍼티를 갖지않는다.
    console.log(A.post.prototype); // undefined
    
    // (생성자)함수 객체로 호출 시 아래와 같은 에러가 발생한다.
    try {
        console.log(new A.post('http://mohwa.com'));
    }
    catch(e){
        /*
         Uncaught TypeError: post(url){
         this.url = url;
         } is not a constructor
        */
        console.log(e.message);
    }

    class 10

  • ES5 를 통한 정적 메서드 선언

    function A(id) {
    }
    
    A.post = function(){
    };
    
    console.dir(A); // A function object
    
    console.log(A.post); // post function object
    
    // (생성자)함수 객체로 호출 시 새로운 객체가 생성된다.
    var newAPostObject = new A.post('http://mohwa.com');
    console.dir(newAPostObject);
    

    A 함수 객체 내부

    class 11

    newAPostObject 객체 내부

    class 0 3

5. 제네레이트 메서드 선언

  • 제네레이트 메서드 선언

    // 클래스 선언
    class A {
    
        constructor(){
        }
    
        // 제네레이터 메서드 선언
        * post(){
            yield 1;
            yield 2;
            yield 3;
        }
    }
    
    console.dir(A);
    
    // 선언된 제네레이터 메서드는 A 클래스의 prototype 객체 프로퍼티에 할당된다.
    console.dir(A.prototype.post); // post function object
    
    // 제네레이터 메서드의 (생성자)함수 객체로 GeneratorFunction 함수 객체를 반환한다.
    console.dir(A.prototype.post.constructor); // GeneratorFunction function object 
    
    var newAObject = new A;
    var generator = newAObject.post();
    
    console.dir(newAObject);
    
    console.log(generator.next()); // 1
    console.log(generator.next()); // 2
    console.log(generator.next()); // 3
    console.log(generator.next().done); // true

    A 클래스 내부(선언된 제네레이터 메서드는 A 클래스의 prototype 객체 프로퍼티에 할당된다)

    class 0 5

    newAObject 객체 내부(제네레이터 메서드의 (생성자)함수 객체로 GeneratorFunction 함수 객체를 반환한다)

    class 0 4

6. 클래스 상속

  • ES6 에서는 extends 절 및 super 키워드를 통해 상속을 구현할 수 있다.

    function A(id){
        // 이 경우 this 는 new C 를 통해 생성된 객체를 가리킨다.
        this.id =  id;
    }
    
    A.prototype.getId = function(){
        return this.id;
    };
    
    // 클래스가 아닌 함수 객체에 대한 상속도 가능하다.
    class B extends A {
    
        constructor(id, name){
    
            // super 키워드를 통해 인스턴스 맴버 초기화 및 위임 과정이 발생한다.
            super(id);
    
            // 이 경우 this 는 new C 를 통해 생성된 객체를 가리킨다.
            this.name = name;
        }
    
        getName(){
            return this.name;
        }
    }
    
    // 클래스 상속
    class C extends B {
    
        constructor(id, name, age){
    
            // super 키워드를 통해 인스턴스 맴버 초기화 및 위임 과정이 발생한다.
            super(id, name);
    
            // 이 경우 this 는 new C 를 통해 생성된 객체를 가리킨다.
            this.age = age;
        }
    
        getAge(){
            return this.age;
        }
    }
    
    console.log(new C('yanione', 'mohwa', 35));
  • chrome 48 버전의 결과

    class 12

  • chrome 49 버전을 통해 다시 확인해본결과, 이전 결과와 달라진것을 볼 수 있다.

    • 인스턴스 이름A ==> C 로 바뀐것은 맞는듯한데, 그 하위의 prototype chain 이름들이 상이?한듯 하다.(즉 C 인스턴스의 __proto__ 속성이 C.prototype 이 아닌, B.prototype 을 참조하고 있다고 출력하고 있다.(하지만 보여지는 prototype 객체 내부에는, C 클래스의 프로토타입 메서드인 getAge 를 포함하고 있는것을 볼 수 있다))

      class 12 1

  • ES5 를 통해 클래스 상속을 구현해본 예(ES6 에서의 super 키워드는 아래 this.__proto__.constructor.call 과 같은 일종의 매크로 구현으로 볼 수 있다)

    function A(id){
        this.id =  id;
    }
    
    A.prototype.getId = function(){
        return this.id;
    };
    
    
    function B(id, name){
    
        // call 메서드를 통해 A (생성자)함수 객체(this.__proto__.__proto__.__proto__)가 가진 인스턴스 맴버를 초기화한다.
        this.__proto__.__proto__.__proto__.constructor.call(this, id);
    
        this.name =  name;
    }
    
    // B.prototype 객체 프로퍼티에 A.prototype (원형)객체를 포함한 새로운 객체를 할당한다.
    B.prototype = Object.create(A.prototype);
    B.prototype.getName = function(){
        return this.name;
    };
    
    B.prototype.constructor = B;
    
    function C(id, name, age){
    
        id = id || '';
        name = name || '';
        age = age || 0;
    
        // call 메서드를 통해 B (생성자)함수 객체(this.__proto__.__proto__)가 가진 인스턴스 맴버를 초기화한다.
        this.__proto__.__proto__.constructor.call(this, id, name);
    
        this.age = age;
    }
    
    // C.prototype 객체 프로퍼티에 B.prototype (원형)객체를 포함한 새로운 객체를 할당한다.
    C.prototype = Object.create(B.prototype);
    C.prototype.getAge = function(){
        return this.age;
    };
    
    C.prototype.constructor = C;
    
    
    var newCObject = new C('yanione', 'mohwa', 35);
    
    console.log(newCObject.getId()); // yanione
    console.log(newCObject.getName()); // mohwa
    console.log(newCObject.getAge()); // 35
    
    console.log(newCObject);

    class 13

  • 파생 클래스constructor(생성자) 내부에서는 반드시 super 키워드가 호출되어야한다.

    function A(id){
    
        this.id =  id;
    }
    
    A.prototype.getId = function(){
      return this.id;
    };
    
    // 클래스가 아닌 함수 객체에 대한 상속도 가능하다.
    class B extends A {
    
        constructor(id, name){
    
            // 파생 클래스의 constructor 내부에서는 super 키워드만을 사용(호출)하거나, this 키워드 사용전에 반드시 호출되어야한다.
            // 즉 constructor 내부에서는 super 키워드가 반드시 호출되어야한다.
        }
    
        getName(){
            return this.name;
        }
    }
    
    try{
        console.log(new B('yanione', 'mohwa'));
    }
    catch(e){
        // 아래와 같은 예외가 발생한다.
        console.log(e.message); // this is not defined
    }
  • super 키워드가 this 처리 후에 호출될 경우, 예외가 발생된다.(반드시 this 사용전에 호출되어야한다)

    function A(id){
        this.id =  id;
    }
    
    A.prototype.getId = function(){
        return this.id;
    };
    
    // 클래스가 아닌 함수 객체에 대한 상속도 가능하다.
    class B extends A {
    
        constructor(id, name){
    
            // 반드시 this 사용전에 호출되어야한다.
            // super(id);
            
            try{
                this.name = name;
            }
            catch(e){
                // Uncaught ReferenceError: this is not defined
                console.log(e.message);
            }
    
            // 이 경우 super 키워드는 처리되지 않는다.
            super(id);
        }
    
        getName(){
            return this.name;
        }
    }
    
    console.log(new B('yanione', 'mohwa'));
  • super 키워드를 통해, 부모 클래스에 선언된 static method 에 접근 가능하다.

    let A = class A {
    
      constructor() {
      }
      
      static x(){
        console.log('x method');
      }
    }
    
    let B = class B extends A {
    
      constructor(length) {
      
        // 파생 클래스의 constructor 내부에서는 반드시 super 키워드가 호출되어야한다.
        super();
      }
    
      static y() {
    
        // static 메서드 내부 "this" 는 "B class" 와 동일하다.
        console.log(this); // B class
    
    
        // "super" 는 "this.__proto__(A class)" 와 동일하다.
    
        // 즉 this.__proto__.x() 메서드를 통해 호출 가능하다.
        this.__proto__.x(); // x method
        
        // super 를 통한 접근
        super.x(); // x method
      }
    }
    
    // var _B = new B();
    B.y();
  • ES5 를 통해, 위 super 키워드의 특성을 구현해본 예

    // A 함수 객체를 생성한다.
    function A(){
    }
    
    // A 함수 객체의 x static method 를 생성한다.
    A.x = function(){
        console.log('x method');
    };
    
    // B 함수 객체를 생성한다.
    function B(){
    }
    
    // B 함수 객체의 y static method 를 생성한다.
    B.y = function(){
        // this(B function object).__proto__(A function object).x 메서드에 접근 가능하다.
        this.__proto__.x(); // === super.x();
    };
    
    // B 함수 객체의 __proto__ 속성은 A 함수 객체를 참조하고있다.
    B.__proto__ = A;
    
    B.y(); // x method
  • super 키워드를 통해, 부모 클래스에 선언된 prototype method 에 접근 가능하다.

    let A = class A {
    
        constructor(id) {
          this.id = id;
        }
    
        x(){
          console.log('x method');
        }
    }
    
    let B = class B extends A {
    
        constructor(id, name) {
        
          super(id);
        
          this.name = name;
        }
    
        y() {
        
          // this.__proto__(B.prototype).__proto__(A.prototype).x()
          this.__proto__.__proto__.x(); // x method
        
          // super 키워드는 this.__proto__.__proto__ 와 같다.
          super.x(); // === this.__proto__.__proto__.x() === x method
        }
    }
    
    
    let _B = new B('yanione', 'mohwa');
    
    console.dir(_B); // {id: "yanione", name: "mohwa"}
    
    _B.y();
  • ES5 를 통해, 위 super 키워드의 특성을 구현해본 예

    // A 함수 객체를 생성한다.
    function A(id){
    
        this.id = id;
    }
    
    // A 함수 객체의 x static method 를 생성한다.
    A.prototype.x = function(){
        console.log('x method');
    };
    
    // B 함수 객체를 생성한다.
    function B(id, name){
    
        // this.__proto__.__proto__.constructor(A function object)
        this.__proto__.__proto__.constructor.call(this, id);
        this.name = name;
    }
    
    // B.prototype 에, A.prototype 을 원형으로 갖는 새로운 객체를 할당한다.
    B.prototype = Object.create(A.prototype);
    
    // y prototype method 를 생성한다.
    B.prototype.y = function(){
    
        // this(B instance).__proto__(B.prototype).__proto__(A.prototype).x
        this.__proto__.__proto__.x(); // === super.x 와 같다 === x method
    };
    
    B.prototype.constructor = B;
    
    var _B = new B('yanione', 'mohwa');
    
    console.log(_B); // {id: "yanione", name: "mohwa"}
    
    _B.y();

Profile picture

Written by mohwa