공부/Javascript

클래스

도리암 2022. 2. 10. 18:35

생성자 함수(prototype)

const riam = {
  firstName: 'Riam',
  lastName: 'Do',
  getFullName: function () {
    return `${this.firstName} ${this.lastName}` // 백틱(그레이브) 기호는 다른 데이터가 들어갈 것을 전제함
  }
}
console.log(riam.getFullName())

const claudette = {
  firstName: 'Claudette',
  lastName: 'Morrel',
  getFullName: function () {
    return `${this.firstName} ${this.lastName}`
  }
}
console.log(claudette.getFullName())

const meg = {
  firstName: 'Meg',
  lastName: 'Tomas',
  getFullName: function () {
    return `${this.firstName} ${this.lastName}`
  }
}
console.log(meg.getFullName())

위 코드처럼 동일한 로직의 여러 객체를 만들면 각각의 객체나 함수 구조를 여러개 만들어 저장하므로 메모리 효율이 좋지 않다.

function User(first, last) {
  this.firstName = first
  this.lastName = last
}
User.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`
}

const dwite = new User('Dwite', 'Fairfield')
const peng = new User('Peng', 'Min')
const jake = new User('Jake', 'Park')

console.log(dwite)
console.log(peng)
console.log(jake)
console.log(dwite.getFullName())

따라서 위의 코드처럼 User와 getFullName 함수를 분리해서 prototype에 선언하면

prototype은 한 번만 만들어지고 함수가 호출될 때마다 '참조'되기만 하므로 메모리 효율이 좋아진다.

Prototype의 원형은 메모리에 한번만 생성. new 키워드를 통해 생성되면 '참조'됨

JavaScript가 프로토타입 기반 언어라고도 불리는 이유.

객체를 생성하면 Prototype 메소드들이 포함돼있다.

객체에 Prototype 메소드가 포함돼있는 모습

** 생성자 함수는 일반 함수와 차이점을 두기 위해 PascalCase로 작성한다. (오랜 관습)

new Swiper('.notice-line .swiper-container', {
  direction: 'vertical', // 수직 슬라이드
  autoplay: true, // 자동 재생 여부
  loop: true // 반복 재생 여부
})

Swiper 함수도 생성자 함수이므로 PascalCase로 작성된 모습.

함수 자체의 내용만으로 동작에 무리가 없으므로 인스턴스는 생략 가능하다.

 

This

일반(Normal) 함수는 호출 위치에 따라서 this가 정의된다.

화살표(Arrow) 함수는 자신이 선언된 '함수' 범위에서 this가 정의된다. (화살표 함수를 감싼 함수를 가리키게 됨)

const riam = {
  name: 'Riam',
  normal: function () {
    console.log(this.name) // this: { name: String; normal: () => void; arrow: ()=> void; }
  },
  arrow: () => {
    console.log(this.name) // this: typeof globalThis | name: any
  }
}
riam.normal() // Riam
riam.arrow()  // Undefined

const claudette = {
  name: 'Claudette',
  normal: riam.normal, // 함수 호출이 아닌, normal에 선언된 데이터 자체를 가져오는 것
  arrow: riam.arrow
}
claudette.normal()  // Claudette
claudette.arrow()   // Undefined

normal() 함수의 경우 호출된 부분이 riam 또는 claudette 객체 내부이므로 this가 각 객체를 가리키게 된다.

반면 arrow() 함수의 경우 선언된 부분 자체가 다른 함수로 감싸져 있지 않으므로 this가 전역(global)을 가리키게 된다.

function User(name) {
  this.name = name
}
User.prototype.normal = function () {
  console.log(this.name)
}
User.prototype.arrow = () => {
  console.log(this.name)
}

const heropy = new User('Riam')

heropy.normal() // Riam
heropy.arrow()  // Undefined

Prototype을 이용하여 작성한 예문이며 이 예문도 똑같은 결과가 도출된다.

 

const timer1 = {
  name: 'Riam!',
  timeout: function() {
    //setTimeout(함수, 시간) // 여기서의 함수를 callback 함수라고 한다.
    setTimeout(function() {
      console.log(this.name) // setTimeout이라는 함수의 로직 어딘가에서 실행되므로 this가 지정되지 않는다.
    }, 2000)
  }
}
timer1.timeout() // 2초 후 Undefined 출력

const timer2 = {
  name: 'Riam!',
  timeout: function() {
    setTimeout(()=>{
      console.log(this.name) // 화살표 함수를 감싸는 setTimeout이라는 함수가 위치한 객체를 가리킨다.
    }, 2000)
  }
}
timer2.timeout() // 2초 후 Riam! 출력

Timer 등의 함수를 사용하게 되면 callback함수를 이용해야 하는데

일반 함수를 사용하면 callback함수가 직접적으로 호출되는게 아니므로 this를 사용할 수 없다.

이 때 사용가능한 것이 화살표 함수

화살표 함수의 this는 화살표 함수를 감싸는 함수가 위치한 객체를 가리키게 되므로

상기 코드에서 가리키게 되는것은 timer2이다.

 

정리하면 객체의 메소드에서 this를 사용하려면 일반 함수를 이용하고

Timer 등의 함수 내부에 사용되는 Callback 함수에서 this를 사용하려면 화살표 함수를 이용해야 한다.

 

ES6 Classes

기본적으로 JavaScript에는 Class 개념이 존재하지 않고 대신에 Prototype개념이 존재함.

ES6에서 Prototype개념을 이용한 Class라는 문법이 추가됨

호이스팅이 되지 않으므로 주의할 것.

// Class로 변환 전
function User(first, last) {
  this.firstName = first
  this.lastName = last
}
User.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`
}
//Class로 변환 후
class User {
  constructor(first, last) {
    this.firstName = first
    this.lastName = last
  }
  getFullName(){
    return `${this.firstName} ${this.lastName}`
  }
}

const riam = new User('Riam', 'Do')
const claudette = new User('Claudette', 'Morrel')
const jake = new User('Jake', 'Park')

console.log(riam)			// Riam
console.log(claudette.getFullName())    // Claudette Morrel
console.log(jake.firstName)		// Jake

constructor는 클래스를 초기화하는 역할을 하며 이게 없으면 인스턴스를 생성할 수 없다.

 

상속(확장)

class Vehicle {
  constructor(name, wheel){
    this.name = name
    this.wheel = wheel
  }
}
class Bicycle extends Vehicle {
  constructor(name, wheel){
    super(name, wheel) //super는 부모 요소를 의미
  }
}
class Car extends Vehicle {
  constructor(name, wheel, license){
    super(name, wheel)
    this.license = license
  }
}

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

const myBicycle = new Bicycle('바이크', 2)
console.log(myBicycle)			// Bicycle {name: '바이크', wheel: 2}

const myCar = new Car('차', 4, '1종보통')
console.log(myCar)			// Car {name: '차', wheel: 4, license: '1종보통'}
console.log(myCar.license)		// 1종보통