JavaScript: JS 클래스 (생성자 함수, this, ES6 Classes, 상속(확장))

생성자 함수 (prototype)


객체데이터의 구조

  • firstName, lastName: 속성
  • getFullName: 메소드, 속성에 함수가 부여되어져 있다면 더이상 속성이 아닌 메소드라고 부른다!
  • 멤버 (Member): 속성과 메소드를 합쳐서 부르는 말이다.
  • this == soha
    this는 해당 객체를 지칭한다.

    const soha = {
      firstName: 'Soha',
      lastName: 'Choe',
      getFullName: function () {
        return `${this.firstName} ${this.lastName}`
      }
    }
    
    console.log(soha.getFullName())
    // Soha Choe
    
    const amy = {
      firstName: 'Amy',
      lastName: 'Clarke',
      getFullName: function () {
        return `${this.firstName} ${this.lastName}`
      }
    }
    
    console.log(amy.getFullName())
    // Amy Clarke
    

동일한 로직 반복해서 찍어낼 때의 단점

중괄호를 사용하는 객체데이터를 하나씩 만들때마다 메모리에 저장이 된다. 그렇게 저장된 객체데이터 내부에 하나의 함수 또한 데이터이다. 로직이 동일함에도 불구하고 우리가 만들어내는 객체데이터의 갯수만큼 함수도 메모리에 계속 할당된다.
즉, 메모리 낭비가 된다는 말이다.
이럴때 사용할 수 있는 것이 자바스크립트의 클래스이다.

클래스 이용하기

자바스크립트의 클래스는 프로그래밍 언어에서 사용하는 클래스와는 조금 다르다.

  • 생성자 함수
    생성하는 함수로, 하나의 객체 데이터가 생성된다.

    function user(first, last) {
      this.firstName = first
      this.lastName = last
    }
      
    const soha = new user('Soha', 'Choe')
      
    console.log(soha)
    // user {firstName: "Soha", lastName: "Choe"}
    
    • soha라는 변수에 user라는 함수를 실행해서 첫번째 변수 ‘Soha’, first에, 두번째 인수 ‘Choe’를 last에 인수로 전달한다.
    • new라는 키워드를 통해서 user 함수를 실행하게 되었다. 이때 실행한 함수(user)를 생성자 함수라고 부른다.
  • 리터럴
    특정한 과정을 거치지 않고 손쉽게 해당하는 데이터를 생성하는 것이다.
    soha 라는 변수에 할당 연산자를 통해서 중괄호를 열고 닫는 행위를 위의 코드(function user …)처럼 복잡하게 처리를 해야 한다. 하지만 중괄호라는 특정 기호를 통해 손쉽게 한번에 만들어 낸다.
    특정한 기호를 가지고 데이터를 만들어 내는 것을 리터럴 방식이라고 한다.
    ex) 문자 데이터를 “”(따옴표)만을 가지고 만들어 내는 것, {} 사용한 객체 데이터, [] 사용한 배열 데이터 등등

    const soha = {}
    
  • 인스턴스
    function user(first, last) {
      this.firstName = first
      this.lastName = last
    }
      
    const soha = new user('Soha', 'Choe')
    const amy = new user('Amy', 'Clarke')
    const neo = new user('Neo', 'Smith')
      
    console.log(soha)
    console.log(amy)
    console.log(neo)
    // user {firstName: "Soha", lastName: "Choe"}
    // ...
    
    • this 라는 것은 생성자 함수(user)를 통해서 그것이 할당되어져 있는 앞의 객체 부분(soha …)의 그 내용을 지칭하는 것이다.
    • new라는 키워드를 통해서 생성자 함수로 실행한 결과(’Soha’, ‘Choe’)를 반환해서 할당된 변수(soha)를 생성자 함수의 인스턴스라고 부른다.
      soha, amy, neo 모두 인스턴스들이다.
  • prototype
    function User(first, last) {
      this.firstName = first
      this.lastName = last
    }
    User.prototype.getFullName = function () {
      return `${this.firstName} ${this.lastName}`
    }
      
    const soha = new User('Soha', 'Choe')
    const amy = new User('Amy', 'Clarke')
    const neo = new User('Neo', 'Smith')
      
    console.log(soha.getFullName())
    console.log(amy)
    console.log(neo)
    // Soha Choe
    // user {firstName: "Soha", lastName: "Choe"}
      // firstName: "Amy"
      // lastName: "Clarke"
      // __proto__: Object
        // getFullName: f ()
        // ...
    // ...
    
    • 기존 방식은 user라는 생성자 함수가 실행될때마다 다른 내용(’Soha’, ‘Neo’ 등)이 들어올 수 있기 때문에 통일해서 관리하기는 조금 어렵다.
    • getFullName은 로직(${this.firstName} ${this.lastName})이 동일하기 때문에 통일화하여 메모리를 효율적으로 관리할 수 있다.
    • user라는 함수에 숨어져있는 prototype 속성에 getFullName을 할당해주면 몇개의 객체를 만들던지 간에 해당 함수는 메모리에 단 한번만 만들어진다.
    • console.log를 이용해 getFullName를 사용할때마다 함수가 만들어지는 것이 아닌 만들어진 함수를 참조하여 출력한다.

자바스크립트의 클래스

prototype을 사용해서 new라는 키워드와 함께 생성자 함수로 인스턴스를 만들어 내는 개념들을 자바스크립트의 클래스라고 부른다.

파스칼 케이스 == 생성자 함수

함수 이름을 파스칼 케이스로 작성한다. 이 말은 즉, 함수가 new라는 키워드와 함께 생성자로 사용되는 것이라는 걸 알 수 있다.

this


일반 (Normal) 함수 내부에서는 호출 위치에서 따라서 this를 정의한다.
화살표 (Arrow) 함수에서는 자신이 선언된 함수 범위에서 this를 정의한다.

const soha = {
  name: 'Soha',
  normal: fuction () {
    console.log(this.name)
  },
  arrow: () => {
  console.log(this.name)
  }
}
soha.normal() // Soha
soha.arrow() // undefined
  • normal의 this
    normal 부분의 함수는 normal이 호출되는 위치에서 this가 정의된다.
    normal 호출되는 위치 앞 객체데이터는 soha이기 때문에 soha가 곧 this이다. 거기에서 name을 꺼내 쓰는 것이기 때문에 name: ‘Soha’가 출력되는 것이다.

  • arrow의 this
    arrow는 본인이 선언된 범위에서 this를 정의한다. 하지만 해당 코드에서는 범위 외부에 함수가 보이지 않기 때문에 undefined가 출력된다.
    화살표 함수로 this를 사용할때 코딩하는 환경에 따라서 this가 다양한 것을 지칭할 수 있다.

  • + 번외: 소괄호를 사용하지 않았을 때

    const amy = {
      name: 'Amy',
      normal: soha.normal,
      // 소괄호를 사용하지 않기 때문에 호출하는 것이 아님
      // soha에 있는 normal 데이터 부분을 amy의 normal에 할당
      arrow: soha.arrow
    }
    amy.normal() // Amy
    amy.arrow() // undefined
    

생성자 함수에서의 this 정의 1

  function User(name) {
    this.name = name
  }
  User.prototype.normal = function () {
    console.log(this.name)
  }
  User.prototype.arrow = () => {
    console.log(this.name)
  }
  
  cost soha = new User('Soha')
  
  soha.normal() // Soha
  soha.arrow() // undefined
  • 일반함수 this
    normal 메소드가 실행되는 호출 위치와 연결되어져 있는 객체(soha), 즉 생성자로 만들어진 soha 인스턴스에 연결된다. soha가 곧 this라는 키워드가 되는 것이고 soha의 name부분에 밖에서 들어온 ‘Soha’라는 문자 데이터가 매개변수로 받아져서 name에 할당된다.
  • 화살표함수 this
    화살표함수가 선언된 범위에 this를 참조하는데 영역에 함수가 따로 없기 때문에 undefined가 출력된다.

생성자 함수에서의 this 정의 2

  const timer = {
    name: 'Soha!!',
    timeout: function () { // 일반함수
      setTimeout(function () {
        console.log(this.name)
      }, 2000)
    }
  }
  timer.timeout() // undefined
  • this가 사용된 부분은 일반함수로 만들어져 있다.
    setTimeout이라는 함수의 내부 로직으로 콜백이 들어가서 실행된다.
  const timer = {
    name: 'Soha!!',
    timeout: function () { // 일반함수
      setTimeout(() => {
        console.log(this.name)
      }, 2000)
    }
  }
  timer.timeout() // Soha!!
  • this가 사용된 부분을 화살표 함수로 변경했다.
    화살표 함수를 감싸는 timeout함수(메소드를 정의할 때 사용한 함수)가 함수의 범위가 된다. 함수 범위는 timeout이고 timeout의 this는 곧 일반함수가 정의된 timer라는 객체데이터를 가리킨다.
    즉, 여기서의 this는 timer가 된다. timer에서의 name은 Soha!!이다.

일반함수 생략화

: function을 생략해도 사용이 가능하다.

  const soha = {
    name: 'Soha',
    normal() { // fuction 부분 생략해도 사용 가능!
      console.log(this.name)
    },
    arrow: () => {
    console.log(this.name)
    }
  }
  soha.normal() // Soha
  soha.arrow() // undefined

결론

어떤 것이 더 좋고 나쁘다는 것은 없다. 상황에 따라서 일반함수, 화살표 함수를 잘 활용하여 사용하자!

ES6 Classes


생성자 함수를 Class키워드를 통해 새롭게 갱신하기

  • 기존 코드
    function User(first, last) {
      this.firstName = first
      this.lastName = last
    }
    User.prototype.getFullName = function () {
      return `${this.firstName} ${this.lastName}`
    }
      
    const soha = new User('Soha', 'Choe')
    const amy = new User('Amy', 'Clarke')
    const neo = new User('Neo', 'Smith')
      
    console.log(soha.getFullName())
    console.log(amy)
    console.log(neo)
      
    // Soha Choe
    // user {firstName: "Soha", lastName: "Choe"}
      // firstName: "Amy"
      // lastName: "Clarke"
      // __proto__: Object
        // getFullName: f ()
        // ...
    // ...
    
  • Class 키워드 활용
    class User {
      constructor(first, last) {
        this.firstName = first
        this.lastName = last
      }
      getFullName() {
        return `${this.firstName} ${this.lastName}`
      }
    }
      
    const soha = new User('Soha', 'Choe')
    const amy = new User('Amy', 'Clarke')
    const neo = new User('Neo', 'Smith')
      
    console.log(soha.getFullName())
    console.log(amy)
    console.log(neo)
      
    // Soha Choe
    // user {firstName: "Soha", lastName: "Choe"}
      // firstName: "Amy"
      // lastName: "Clarke"
      // __proto__: Object
        // constructor: f ()
        // getFullName: f ()
        // ...
    // ...
    

    클래스 키워드를 사용해도 이전과 같이 동일한 출력결과가 나온다.

상속 (확장)


새로운 클래스를 만들때, 이미 정의가 되어져 있는 클래스를 확장(상속)해서 굉장히 간단하게 구현을 할 수 있다.

class Vehicle {
	constructor(name, wheel) {
		this.name = name
		this.wheel = wheel
	}
}
const myVehicle = new Vehicle('운송수단', 2)
console.log(myVehicle) 
// Vehicle {name: "운송수단", wheel: 2}
	// name: "운송수단"
	// wheel: 2

class Bicycle extends Vehicle {
	constructor(name, wheel) {
		super(name,wheel)
	}
}
const myBicycle = new Bicycle('삼천리', 2)
const daughtersBicycle = new Bicycle('세발', 3)
console.log(myBicycle)
console.log(daughtersBicycle)
// Bicycle {name: "삼천리", wheel: 2}
	// name: "삼천리"
	// wheel: 2
// Bicycle {name: "세발", wheel: 3}
	// name: "세발"
	// wheel: 3

class Car extends Vehicle {
	constructor(name, wheel, license) {
	super(name, wheel)
	this.license = license
	}
}
const myCar = new Car('벤츠',4, true)
const daughersCar = new Car('포르쉐', 4, false)
console.log(myCar)
console.log(daughtersCar)
// Car {name: "벤츠", wheel: 4, license: ture}
	// license: true
	// name: "벤츠"
	// wheel: 2
// Car {name: "포르쉐", wheel: 4, license: false}
	// license: false
	// name: "포르쉐"
	// wheel: 3
  • extends
    확장(상속)을 의미하는 키워드이다.
    extends 앞에는 새로운 클래스 명, 뒤에는 확장할 부모 클래스 명을 적는다.
  • super()
    super()은 extends 키워드 뒤에 붙어있는 확장된 클래스 (Vehicle)을 의미한다. super가 있는 자리에서 Vehicle이 실행된다.
  • 진정한 의미의 확장
    기존 클래스의 내용을 extends와 super을 통해 가져온 다음에 추가적인 내용을 작성하는 것이 진정한 확장이다.
    Vehicle (운송수단)에서 Car(자동차)로 확장이다.

Comments