JS) Class

kangdari·2020년 4월 3일
2

Class

ES5 방식

// ES5
function Person1(name) {
    this.name = name;
}
// prototype method
Person1.prototype.getName = function(){
    return this.name;
}
// static method, intance 에서는 호출 불가
Person1.isPerson = function (obj){
    return obj instanceof this
}
const k = new Person1('kang') // instance 생성
console.log(k.getName()) // kang
console.log(Person1.isPerson(k)) // true

// instance
// person.getName() // (o)
// person.isPerson() // (x)

// Person1.getName() // (x)
// Person1.isPerson() // (o)

ES6 Class

class Person2 {
    constructor (name) { this.name = name }
    getName () { return this.name }
    static isPerson (obj) { return obj instanceof this } // static method
}
const k2 = new Person2('kang2')
console.log(k2.getName()) // kang
console.log(Person2.isPerson(k2)) // true

선언 방식

// 클래스 리터럴 , 클래스는 식 or 값.
class Person1 { }
console.log(Person1.name) // Person1

// 기명 클래스 표현식
const Person2 = class Person22 {  }
console.log(Person2.name) // Person22

// 익명 클래스 표현식
const Person3 = class { }
console.log(Person3.name) // Person3

기존 방식과의 차이점

  • let과 const처럼 TDZ이 존재하며 블록스코프에 갇힌다.
if(true) {
    class A {}
    const a = new A() // o

    if(true) {
        // 변수 A만 호이스팅 되고, 할당은 x -> TDZ
        const b = new A() // TDZ Error
        class A {}
    }
}

const c = new A() // referenceError, A는 if
  • class 내부는 strict mode가 강제된다.

  • 모든 메소드는 열거할 수 없다.

class A {
    a () {}
    b () {}
    static c () {}
}
for(let p in A.prototype){
    console.log(p) // undefined , class 내부 메소드들이 열거 안됨.
}
  • constructor를 제외한 모든 메소드들은 new 명령어로 호출할 수 없다.
class A {
    constructor () {}
    a () {}
    static c () {}
}

const a = new A.prototype.constructor() // A {}
const b = new A.prototype.a() // Uncaught TypeError: A.prototype.a is not a constructor
const c = new A.prototype.c()  // Uncaught TypeError: A.prototype.c is not a constructor

A.prototype.constructor === A // true A의 constructor는 자기 자신(A)과 같다.

A 클래스 자체에는 prototype 있기 때문에 new로 호출이 가능하다.

A 클래스의 constructor()에는 prototype이 있기 때문에 마찬가지로 new로 호출이 가능하다.

반면에 A클래스의 a () 메소드에는 prototype이 없기 때문에 new로 호출이 불가능하다.

  • 클래스는 오직 생성자로서만 동작한다.
class A {}
new A() // A {}
A() // Uncaught TypeError: Class constructor A cannot be invoked without 'new'

// 기존 방식은 생성자, 함수로 사용 가능
function AA () {}
new AA() // AA () {}
AA() // AA () {}
  • 클래스 내부에서 클래스명 수정
// 익명 클래스 표현식
let A = class {
    constructor () { A = 'A' }
}
const a = new A()
console.log(A) // 'A' 변경됨

// 클래스 선언문 방식,
class C {
	constructor () { C = 'C' }
}
const c = new C() // Uncaught TypeError: Assignment to constant variable.
// 내부에서 C를 변경할려는 경우 C는 상수로 취급하기 때문에 수정 불가

C = '20' // 20
// 외부에서 C를 변경하는 경우 C는 let으로 취급하여 수정 가능
  • 외부에서 prototype 전체를 다른 객체로 덮어씌울 수 없다.(읽기 전용)
    prototype 전체를 변경하는건 불가능하지만 prototype의 메소드를 하나 하나 직접 수정은 가능하다.
class A {
    a () {}
}
// prototype 전체를 덮어 씌우기 시도 -> 변화 없음
A.prototype = {
    b() {console.log(1)} 
}

// prototype의 메소드 하나 하나는 수정이 가능
a.a() // undefined
A.prototype.a = function() { console.log(2)}
a.a() // 2

반면에 기존 방식에서는 prototype 전체 수정이 가능했다.

function B () {}

B.prototype = { 
    a() { console.log(1)}
}
  • 문이 아니고 식 or 값이다.

식, 값이기 때문에 class를 다른 함수에 인자로 넘길 수 있다.

const instanceGenerator = (className, ...params) => new className(...params)

class Person {
    constructor (name) { this.name = name }
    getName () { console.log(this.name)}
}
// class를 인자 값으로 전달 함.
const k = instanceGenerator(Person, 'kang')

k.getName(); // kang
  • computed property names

객체 리터럴 표기법의 객체 속성 이름을 동적으로 설정이 가능, 계산된 프로퍼티 이름 설정이 가능

const method1 = 'sayName';
const fullNameGetter = 'fullName'

class Person {
    constructor (name) { this.name = name }
    // 메소드 이름을 동적으로 설정
    [method1] () { console.log(this.name) }
    get [fullNameGetter + 123] () { return this.name + 'sang'}
}
const k = new Person('kang')

k.sayName() // kang
k.fullName123 // "kangsang"
  • 클래스에서 제너레이터 선언
class A {
    // 객체와 마찬가지로 *을 앞에 적음
    *gene () {
        yield 1;
        yield 2;
    }
}

const a = new A();
const iter = a.gene();
console.log(...iter) // 1 2 
iter.next() // {value: 1, done: false}
iter.next() // {value: 2, done: false}
  • 클래스에서 Symbol.iterator
class Products {
    constructor () {
        this.items = new Set()
    }
    addItem (name) {
        console.log(this)
        this.items.add(name)
    }
    [Symbol.iterator] () { 
        let count = 0
        const items = [...this.items]
        return {
            next () {
                retur
                    done: coun{nt >= items.length,
                    value: items[count++]
                }
            }
        }
    }
}
const a = new Products(); // Set 타입의 this.items 생성
a.addItem('사과') // 데이터 추가
a.addItem('수박')
[...a] // ['사과', '수박']
  • Symbol.iterator를 제네레이터로 만들기
class Products {
    constructor () {
        this.items = new Set()
    }
    addItem (name) {
        console.log(this.items)
        this.items.add(name)
    }
    *[Symbol.iterator] () { 
        yield* this.items 
    }
}
  • 정적(static) 메소드
    static method는 클래스 본인으로만 호출할 수 있다. 인스턴스는 불가능하다.
class Person {
    static create (name) {
        return new this(name)
    }
    constructor (name) {
        this.name = name
    }
}
const k = Person.create('kang')
k // Person {name: "kang"}

const kk = new Person('kkang')
kk // Person {name: "kkang"}

상속(inherit)

소개

super 메소드: 상위 클래스의 constructor를 호출. 오직 constructor안에서만 사용 가능

class Square { 
    constructor (width) {
    	// this는 인스턴스 or super 메소드를 호출한 인스턴스
        this.width = width
    }
    getArea () { 
        return this.width * ( this.height || this.width )
    }
}

class Rectangle extends Square {
    constructor (width, height){
        // super: 상위 클래스의 constructor를 호출하는 함수
        // 오직 constructor안에서만 호출이 가능하다.
        super(width)
        // this: Rectangle,  this.width = width
        this.height = height
    }
}

const rect = new Rectangle(10, 30)
rect.getArea() // 300

상세

  1. class [서브클래스명] extends [수퍼클래스명] { [서브클래스 본문] }

    • 변수말고도 클래스 식이 와도 된다. -> 쓸 일 없음
    class Employee extends class Person {
    constructor (name) { this.name = name }
    } {
    // subClass 본문
    constructor (name, position) {
        super(name)
        this.position = position
    	}
    }
    
    const k = new Employee('kang', 'worker')
    
    console.log(k) // Employee { name: 'kang', position: 'worker' }
    console.log(k.__proto__.__proto__.constructor.name) // Person
  • 함수도 상속 가능
function Person (name) { this.name = name }
    
class Employee extends Person {
// subClass 본문
constructor (name, position) {
    super(name)
    this.position = position
    }
}

const k = new Employee('kang', 'worker')
  • 내장 타입 상속 가능

toString()를 처리하는 과정을 바꿔서 사용
추가하고 수정하고자 것만 설정하고 나머지는 그대로 사용 가능

class newArray extends Array {
    toString () {
        return `[${super.toString()}]`
    }
}
const arr = new newArray(1,2,3)
arr.toString() // "[1,2,3]"
arr.pop() // 3

[1,2,3].toString() // "1,2,3"

super(내부 키워드로써, 허용된 동작 외엔 활용 불가)

  1. constructor 내부에서

    • 수퍼클래스의 constructor를 호출하는 함수 개념
    • 서브클래스의 constructor 내부에서 this에 접근할려고 할 때는
      가장 먼저 super 함수 호출해야함
      .
    • 서브클래스에서 constructor를 사용하지 않는다면 무관.
      ( 이 경우 상위클래스의 constructor만 실행됨.)
  2. 메소드 내부에서

    • 수퍼클래스의 프로토타입 객체 개념
    • 메소드 오버라이드 또는 상위 메소드 호출 목적으로 활용
  • super는 출력할 수 없고 사용만 가능하다.
class Rectangle {
    constructor(width, height) {
        this.width = width
        this.height = height
    }
    getArea () {
        return this.width * this.height
    }
}

class Square extends Rectangle {
    constructor(width){
        super(width, width); // 가로 === 세로
    }
    getArea () {
        console.log('getArea of Square')
        return super.getArea()
    }
}

const square = new Square(5)
console.log(square.getArea()) // getArea of Square 25
  • new.target을 이용한 abstract(추상) class 흉내
class Shape {
    constructor () {
        // new로 Shape 인스턴스를 생성한 경우
        if(new.target === Shape){
            throw new Error('이 클래스는 인스턴스화할 수 없는 추상클래스임.')
        }
    }
}

class Rectangle extends Shape {
    constructor(width, height) {
        this.width = width
        this.height = height
    }
    getArea () {
        return this.width * this.height
    }
}

class Square extends Rectangle {
    constructor(width){
        super(width, width); // 가로 === 세로
    }
    getArea () {
        console.log('getArea of Square')
        console.log
        return super.getArea()
    }
}

const s = new Shape() // error, Shape 클래스는 instance화 불가능
const r = new Rectangle(2, 5)

1개의 댓글

comment-user-thumbnail
2020년 10월 29일

좋은글 감사합니다 :-)

답글 달기