Frontend/JS.info 정리

class - class 기본 문법

Creative_Lee 2022. 6. 20. 21:21

1. class

Class를 사용하면 객체 지향 프로그래밍에서 사용되는 다양한 기능을 자바스크립트에서도 사용 할 수 있습니다.

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

  welcome(){
    console.log(`Welcome ${this.name}`)
  }
}

let user1 = new User({name : 'Bob'});

user1.welcome(); // 'Welcome Bob'

클래스를 new 와 함께 호출하면 다음과 같은 일이 일어납니다.

 

1. 새로운 객체가 생성됩니다.

2. 넘겨받은 인수와 함께 constructor가 자동 실행됩니다.

 

constructor() 는 new에 의해 자동 호출되므로, 별다른 동작없이 객체를 초기화 할 수 있습니다.


2. 클래스는 함수이다.

console.log(typeof User) // function

class가 함수라는 증거입니다.

 

 

class User {...} 문법 구조의 동작은 위 그림과 같습니다.

 

1. User라는 함수를 만듭니다.  함수의 본문은 생성자 메서드인 constructor에서 가져옵니다.

    생성자 메서드가 없다면, 본문이 비워진 함수를 만듭니다.

2. 클래스 내에서 정의한 메소드를 User.prototype에 저장합니다.

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

  welcome(){
    console.log(`Welcome ${this.name}`)
  }
}

let user1 = new User({name : 'Bob'});

console.log(user1.__proto__) // {constructor: ƒ, welcome: ƒ}

new User 호출로 객체를 만들었습니다.

user1 인스턴스의 [[Prototype]]은 User.prototype 입니다.

prototype 상속을 통해

인스턴스 객체에서 User.prototype의 호출할 수 있습니다.

 

console.log(User.prototype.constructor === User) // true
console.log(user1.__proto__ === User.prototype) // true

이러한 원리로 인스턴스 객체에서 class에 정의한 메서드에 접근할 수 있습니다.

 


3. class는 순수 함수로 class를 구현한 것과 엄연히 다르다.

순수 함수로도 class역할을 하는 함수를 선언 할 수 있지만,

다음과 같은 중요한 차이점들이 있습니다.

 

1. class로 만든 함수에는 특수 내부 프로퍼티인 [[ IsClassConstructor ]] : true 가 있습니다.

    이 프로퍼티는 다음과 같은 상황에 class를 구별하는 데 사용됩니다.

    - class 생성자 함수를 new 키워드와 함께 호출했는지 유무에 따라 에러가 발생합니다.

    - class를 문자열로 형변환 하면 class... 로 시작하는 문자열이 됩니다.

 

2. class에 정의된 메서드는 열거할 수 없습니다.

    class의 prototype 프로퍼티에 추가된 매서드의 enumerable 플래그가 false이기 때문입니다.

 

3. class는 항상 엄격 모드로 실행됩니다.

    class 생성자 코드 내부에는 자동으로 엄격 모드가 적용됩니다.

 


4. class 표현식

class도 표현식으로 만들 수 있고, 

기명 함수 표현식과 유사하게 이름을 붙일 수 있으며,

동적으로 생성하는 것도 가능합니다.

let User = class { // class 표현식
  constructor({name}){
    this.name = name;
  }

  welcome(){
    console.log(`Welcome ${this.name}`)
  }
}

let Person = class ImClass { // 기명 class 표현식
  imClass(){
    console.log(ImClass)
  }
}

new Person().imClass()  

// class ImClass {
//   imClass() {
//       console.log(ImClass);
//   }
// }

function makeDynamicClass(msg){
  return class{
    speak(){
      console.log(msg)
    }
  }
}

let Dynamic = makeDynamicClass('Im Dynamic') // 동적 생성
new Dynamic().speak()  // 'Im Dynamic'

 


5. class의 getter, setter, computed property

class도 getter, setter, computed property를 지원합니다.

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

  get name(){
    return this._name;
  }

  set name(value){
    if(typeof value !== 'string'){
      console.log('문자열만 입력하세요!')
      return
    }
    this._name = value;
  }
}

let user1 = new User({name : 'Bob'});
console.log(user1.name); // Bob

let user2 = new User({name : 1}) // '문자열만 입력하세요'
console.log(user2.name) // undefined

getter와 setter도 User.prototype에 정의됩니다.

console.log(User.prototype) 의 결과


class User { // class 표현식
  constructor({name}){
    this.name = name;
  }

  [`hello`](){
    console.log(`hello ${this.name}`);
  }
}

let user1 = new User({name : `Bob`});

user1.hello() // hello Bob

computed property도 사용 가능합니다.


6. class field (클래스 필드)

클래스 필드 문법을 사용하면 어떤 종류의 프로퍼티도 추가할 수 있습니다.

class User {
  constructor(name){
  this.name = name
  }
  
  classFieldHi = () =>{  // class field 
    console.log('hello ' + this.name)
  }
}

let user1 = new User('Bob');

6-1. losing this

class User {
  constructor(name){
    this.name = name
  }

  hi(){
    console.log('hello ' + this.name)
  }
}

let user1 = new User('Bob');

setTimeout(user1.hi, 1000) // hello ???  --> 원하는 결과가 나오지 않습니다.

 

자바스크립트의 this가 동적으로 결정되기 때문에

위 코드의 맨 밑줄과 같이 객체의 메서드를 여기저기 전달해 전혀 다른 컨텍스트에서 호출하면

this는 메서드가 정의된 객체를 참조하지 않습니다.

 

위와 같은 losing this 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다.

 

1. 래퍼 함수 전달하기. 

setTimeout(()=> user1.hi(), 1000) // hello Bob

2. 생성자 안 등에서 메서드를 객체에 바인딩하기.

 

3. 위 코드 예시와 같이 class field 사용하기.

class User {
  constructor(name){
    this.name = name
  }

  hi(){
    console.log(`hello ${this.name}`)
  }

  classFieldHi = () =>{  // class field 
    console.log(`hello ${this.name}`)
  }
}

let user1 = new User('Bob');

console.log(User.prototype) // { constructor: ƒ, hi: ƒ }
console.log(user1) // { name: 'Bob', classFieldHi: ƒ }

setTimeout(user1.hi, 1000) // hello 
// 위 경우에는 this는 window 입니다.
// window.name 은 존재하지 않습니다.

setTimeout(user1.classFieldHi, 1000) // hello Bob
// classFieldHi = () => { ... } 은 각 인스턴스에 독립적인 함수를 만듭니다.
// 함수의 this를 class 객체에 바인딩시켜줍니다.
// 원하는 결과를 얻을 수 있습니다.

위 코드에서 알 수 있는 것처럼

일반적인 메소드는 클래스의 prototype 프로퍼티에 저장되지만,

클래스 필드는 인스턴스 객체에만 저장됩니다.

 

클래스 필드의 이러한 특징을 이용하면

class 객체에 this를 바인딩 할 수 있고 원하는 결과를 얻을 수 있습니다.


6-2. 클래스 필드의 단점.

클래스 필드에는 단점이 존재합니다.

 

1. 클래스 필드는 인스턴스 객체에만 저장되기 때문에 클래스 상속을 사용할 수 없습니다.

2. 클래스 필드는 각 인스턴스 마다 매번 새롭게 생성되기 때문에 메모리를 더 차지하게 됩니다.

3. 테스트케이스 작성시 spyOn( class.prototype, '메소드명' ) 으로 mocking할 수 없습니다.

 

(3번 테스트 케이스의 대한 내용은 추후 테스트 코드에 대한 지식이 생겼을 때 추가하도록 하겠습니다!)

 

위와 같은 이유로 특정 컨텍스트에 this를 바인딩 하려는 명확한 목적이 아니라면,

클래스 필드 사용은 지양하는 것이 좋을 것 같습니다.


요약

1. class는 함수이며, class 내에 정의된 메소드들은 class.prototype 객체에 추가된다.

2. class의 constructor 생성자 메서드는 인스턴스 생성시 객체를 초기화 한다.

3. class와 new를 사용하여 만든 인스턴스 객체는 class명.prototype을 상속 받는다.

4. 위 내용에 따라 인스턴스 객체에서 class에 정의된 메소드를 사용할 수 있다.

5. class 생성자에는 [[ IsClassConstructor ]] : true 라는 내부 프로퍼티가 있으며,

    여러 상황에서 class를 구별하는데에 사용된다.

    또한 class의 메서드는 열거할 수 없고, class는 항상 엄격 모드로 실행된다.

6. 클래스도 표현식 내부에서 정의, 전달, 반환, 할당할 수 있다.

7. class에서도 getter, setther, computed property를 사용할 수 있다.

8. class field 문법은 losing this 와 같은 문제를 해결 해 줄 수 있지만,

    class.prototype에 추가 되지 않고 생성되는 인스턴스 객체 마다 새롭게 생성된다.

    이에 따라 메모리문제, 테스트 케이스 작성 시 특정 함수 사용 불가능 과 같은 문제가 발생된다.

    특정 컨텍스트에 this 바인딩을 하려는 명확한 목적이 있을 대만 사용하는 것이 바람직하다.

 

 

 

 

 

기본이 중요해!!