본문 바로가기
프로그래밍 개발/JS ES6+

Javascript ES6+ Class

by Jinseok Kim 2020. 12. 21.
반응형

 

 

 

Class

 

 

ES5의 프로토타입 메소드 대신 ES6+에서 더 편히라게 쓸 수 있는 Class 가 생겨났다. 

 

 

 

 

 

class의 기본 쓰임

 

// 클래스 리터럴
class Person1 { }
console.log(Person1.name)

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

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

 

 

 

 

 

ES5 버전의 프로토타입

 

function Person1 (name) {
  this.name = name
}
Person1.prototype.getName = function () { //prototype 메소드이다.
  return this.name
}
Person1.isPerson = function (obj) {  //static 메소드이다. isPerson은 Person1에서만 유효하다.
  return obj instanceof this
}
const jin1 = new Person1('jinseok')
console.log(jin1.getName())          //prototype으로 getName과 Person1은 연결되어있다.
console.log(Person1.isPerson(jin1))

//결과:
//jinseok
//true

 

 

 

 

 

 

ES6+의 Class

 

class Person2 { //class을 선언해준다.
    constructor (name) { this.name = name} //생성자 함수의 내용으로 보면 된다.
    getName () { return this.name}         //prototype 메소드 대신 이렇게 쓰면 된다.
    static isPerson (obj) { return obj instanceof this} // static 함수는 이렇게 쓰면 된다.
}
//중요한 것은 class 안은 객체처럼 묶음이 아니라 메소드들을 정의해놓을 수 있는 구역으로 이해해야한다.
const jin2 = new Person2("jinseok")
console.log(jin2.getName())
console.log(Person2.isPerson(jin2))

//결과:
//jinseok
//true

 

 

 

 

 

 

 

 

 let, const와 마찬가지로 TDZ가 존재하며, 블록스코프에 갇힌다.

 

if(true) { //if문이 선언 된 순간 if문 안의 블록 스코프가 생성된다.
    class A {} // Class A의 A는 if문의 블록 스코프 안에서 만 실행된다.
    const a = new A() // 실행 ok
    if(true) {
        const b = new A() // 실행 no, TDZ
        class A{} //Class는 ES6+의 let const 방식을 따르기 때문에 호이스팅이 안되어 미리 위에서
                  // class A{}을 선언해주어야 TDZ에 걸리지 않는다.
    }
}
const c = new A()

 

 

 

 

 

 

 

class 내부는 strict mode가 강제된다. 

모든 메소드를 열거할 수 없다. (콘솔에서 색상이 흐리게 표기됨)

 

class A {
  a () { }
  b () { }
  static c () { }
}

for (let p in A.prototype) {
  console.log(p)            //오류, Class는 모든 메소드를 열거할 수 없다.
}

A.prototype.a = function () { }
A.prototype.d = function () { }

for (let p in A.prototype) {
  console.log(p)           //오류
}

 

 

 

 

 

 

 

constructor를 제외한 모든 메소드는`new` 명령어로 호출할 수 없다.

 

class A {
  constructor () { }
  a () { }
  static b () { }
}
const a = new A.prototype.constructor() //Class A 안의 메소드들은 prototype이 없어 성립되지 않는다.
const b = new A.prototype.a() // 오류
const c = new A.prototype.b() // 오류

 

 

 

 

 

생성자로서만 동작한다.

 

class A { }
A() //오류, Class A안에 new 연산자 없이 A을 쓰니 오류가 난다.

 

 

 

 

 

 

클래스 내부에서 클래스명 수정

let A = class {
  constructor () { A = 'A' }
}
const a = new A()
console.log(A) // 'A' 따로 변수로 Class을 심어준다면 생성 함수가 기능을 발휘한다.

const B = class {
  constructor () { B = 'B' }
}
const b = new B()
console.log(B)  // 오류, 하지만 따로 변수로 선언해주지 않는다면 Class의 B는 단지 상수이다.


class C {
  constructor () { C = 'C' }
}
C = 10; 
//ok, 내부에서 해당 class 명을 바꾸려면 상수로서 바뀌지 않지만 외부에서는 let 변수로서 바뀔 수 있다

 

 

 

 

 

 

클래스 외부에서 클래스명 수정

 

let A = class { }
A = 10;             // ok

const B = class { }
B = 10;             // 오류

class C { }
C = 10;             // ok  

 

 

 

 

 

 

외부에서 prototype을 다른 객체로 덮어씌울 수 없다 (읽기전용)

 

class A {
  a () { }
}
A.prototype = {
  a () { console.log(1) } //prototype으로 class A을 덮어쓰기 시도해본다.
}
const a = new A()
a.a()

// undefined, 그대로 Class A안의 a(0 {}의 빈 객체의 결과인 undefined가 나왔다.

 

 

 

 

 

 

 '문'이 아닌 '식'이다.

 

const instanceGenerator = (className, ...params) => new className(...params)
class Person {
    constructor (name) {this.name = name}
    sayName () {console.log(this.name)}
}

const jin = instanceGenerator(Person, 'jinseok')
const jin2 = instanceGenerator(class {constructor (name) {this.name = name}
sayName () {console.log(this.name)} // 이처럼 값으로 매개변수로서 class을 사용할 수 있다.
}, '진석')

jin.sayName()
jin2.sayName()

//결과:
//jinseok
//진석

 

 

 

 

 

 

computed property names

 

const method1 = 'sayName'
const fullNameGetter = 'fullname'
class Person {
  constructor (name) { this.name = name }
  [method1] () { console.log(this.name) } // class 안에서도 대괄호 표기법이 가능하다.
  get [fullNameGetter] () { return this.name + ' kim' }
}
const jin = new Person('jinseok')
jin.sayName()
console.log(jin.fullname)

//결과:
//jinseok
//jinseok kim

 

 

 

 

 

 

 

제너레이터

 

class A {
  *generator () {
    yield 1
    yield 2
  }
}
const a = new A()
const iter = a.generator()
console.log(...iter)

//결과:
//1 2

 

 

 

 

 

 

Symbol.iterator

 

class Products {
  constructor () {
    this.items = new Set() //Set()으로 선언해주어 중복이 허용되지 않으며 순서를 보장하는
                           //값들로만 이루어진 리스트를 만들어 주었다.
  }
  addItem (name) {
    this.items.add(name)
  }
  [Symbol.iterator] () { // [Symbol.iterator]을 Class 안에 적용할 수 있다.
    let count = 0
	  const items = [...this.items]
    return {
      next () {
        return {
          done: count >= items.length,
          value: items[count++]
        }
      }
      
//결과:
//사과
//배
//포도
    }
  }
}
const prods = new Products()
prods.addItem('사과')
prods.addItem('배')
prods.addItem('포도')
for (let x of prods) {
  console.log(x)
}

 

class Products {
  constructor () {
    this.items = new Set()
  }
  addItem (name) {
    this.items.add(name)
  }
  *[Symbol.iterator] () {
    yield* this.items
  }
}
const prods = new Products()
prods.addItem('사과')
prods.addItem('배')
prods.addItem('포도')
for (let x of prods) {
  console.log(x)
}

//결과:
//사과
//배
//포도

 

 

 

 

 

 

정적 메소드 (static method)

 

class Person {
  static create (name) {
    return new this(name)
  }
  constructor (name) {
    this.name = name
  }
}
const jn = Person.create('진석') //Class 안의 정적 메소드는 무조건 정적 메소드를 품고 있는
                                 //Class만이 접근 가능하다. 프로토타입, this 되지 않는다. 
console.log(jn)

//결과: 
//진석

 

 

 

 

 

 

 

Class의 상속

 

 

 

 

 

ES5에서는 상속에 관하여 코드를 작성하려면 아래와 같이 프로토타입의 상하 관계를 연결시키기 위하여 복잡한 코드를 작성해야 되었다.

 

function Square (width) { //제일 상위급인  Square이다.
  this.width = width
}

Square.prototype.getArea = function () {
  return this.width * (this.height || this.width)
}

function Rectangle (width, height) { //Square보다 하위인  Rectangle이다.
  Square.call(this, width)
  this.height = height
}

function F() { }

F.prototype = Square.prototype //F 함수를 Square.프로토타입으로 변수를 선언하여 연결시켜주었다.
Rectangle.prototype = new F() //  F함수를 생성 함수를 Rectangle.프로토타입의 변수로 지정하였다.
Rectangle.prototype.constructor = Rectangle
//즉 Square을 가지고 있는 생성 F함수의 변수 Rectangle.prototype을 Rectangle과 연결시켜주어
// 상하위 관계인 Rectangle, Square 이 둘을 모두 연결되게 하였다.

const square = Square(3)
const rect = new Rectangle(3, 4)

console.log(rect.getArea())
console.log(rect instanceof Rectangle)
console.log(rect instanceof Square)

//결과:
//12
//true
//true

 

하지만 ES6+으로 업그레이드 되면서 훨씬 간단하고 이해하기 쉽게 코드를 작성하게 되었다.

 

 

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

 

class Square {
  constructor (width) {
    this.width = width
  }
  getArea () {
    return this.width * (this.height || this.width)
  }
}
class Rectangle extends Square { //class 명 뒤에 extends을 붙이고 상위에 있는 Square붙여준다.
  constructor (width, height) {
    super(width)                 //super 함수를 쓰면 자동으로 extends 뒤에 붙여준 Square의
                                 // 'this.width = width'을 가르키게 된다.
    this.height = height         // this 또한 rect(Rectangle의 생성함수 변수).getArea()으로 호출하면 
                                 //Square의 getArea() 메소드 자동으로 연결된다.
                                 
  }
}

const rect = new Rectangle(3, 4)
console.log(rect.getArea())
console.log(rect instanceof Rectangle)
console.log(rect instanceof Square)  // true가 나왔다는 의미는  Rectangle과 Square 연결되었다는 거다.

//결과:
//12
//true
//true

 

 

 

 

 

반드시 변수만 와야 하는 것이 아니라, 클래스 식이 와도 된다.

 

class Employee extends class Person {
  constructor (name) { this.name = name }
} {
  constructor (name, position) {
    super(name)
    this.position = position
  }
}
const jn = new Employee('jinseok', 'student')

 

 

 

 

함수도 상속 가능하다.

 

function Person (name) { this.name = name }
class Employee extends Person {
  constructor (name, position) {
    super(name)
    this.position = position
  }
}
const jin = new Employee('jinseok', 'student')

////

class Employee extends function (name) { this.name = name } {
  constructor (name, position) {
    super(name)
    this.position = position
  }
}
const jin = new Employee('jinesok', 'student')

 

 

 

 

내장 타입 상속 가능

 

class NewArray extends Array {
  toString () {
    return `[${super.toString()}]`
  }
}
const arr = new NewArray(1, 2, 3)
console.log(arr)
console.log(arr.toString()) //대괄호로 감싼 배열 자체 값을 문자로 바꿀 수 있다.

//결과:
//NewArray(3) [1, 2, 3] (배열형식)
//"[1,2,3]" (문자형식)

 

 

 

 

 

 

 

 

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

 

 

  • constructor 내부에서
      - 수퍼클래스의 constructor를 호출하는 함수 개념.
      - 서브클래스의 constructor 내부에서 `this`에 접근하려 할 때는 **가장 먼저** super함수를 호출해야만 한다.
      - 서브클래스에서 constructor를 사용하지 않는다면 무관. (이 경우 상위클래스의 constructor만 실행된다.)거나, 내부에서 `this`에 접근하지 않는다면 무관.

 

  •  메소드 내부에서
      - 수퍼클래스의 프로토타입 객체 개념.
      - 메소드 오버라이드 또는 상위 메소드 호출 목적으로 활용.

 

class Rectangle {
  constructor (width, height) {
    this.width = width
    this.height = height
  }
  getArea () {
    return this.width * this.height
  }
}
class Square extends Rectangle {
  constructor (width) {
   // console.log(super) // super는 접근 및 호출할 수 없다. 내부에서 쓰는 용도 밖에 없다.
    super(width, width) //서브 클래스에서 super을 먼저 쓰고나서 아래부터 this을 써야하는 규칙이 있다.
    //'이제 this 올 수 있음'
 }
  getArea () {
    console.log('get area of square.')
  //  console.dir(super) // super는 접근 및 호출할 수 없다. 내부에서 쓰는 용도 밖에 없다.
    return super.getArea() 
    //서브 클래스 안의  getArea ()와 같은 메소드 내부에서는 메소드 오버라이드 또는 
    //상위 메소드 호출 목적으로 활용한다.

  }
}
const square = new Square(3)
console.log(square.getArea())

//결과:
//get area of square.
//9

 

 

 

 

 

new.target을 활용한 abstract class 흉내

 

 

class Shape {
  constructor () {
    if(new.target === Shape) {
      throw new Error('이 클래스는 직접 인스턴스화할 수 없는 추상클래스입니다.')
    } //슈퍼 클래스의 constructor 메소드 안에 있는 if문 인자에 new.target을 사용하여 new 함수가 오지 않거나 
      //class 명인 shape와 같으면 아예 실행되지 않게 하였다.
  }
  getSize () {}
}
class Rectangle extends Shape {
  constructor (width, height) {
    super()
    this.width = width
    this.height = height
  }
  getSize () {
    return this.width * this.height
  }
}
const s = new Shape()
const r = new Rectangle(4, 5)

 

 

 

 

 

반응형

'프로그래밍 개발 > JS ES6+' 카테고리의 다른 글

Javascript ES6+ Promise  (0) 2020.12.22
Javascript ES6+ Generator  (0) 2020.12.18
Javascript ES6+ Iterator  (0) 2020.12.18
Javascript ES6+ Iterable  (0) 2020.12.18
Javascript ES6+ Map  (0) 2020.12.16

댓글