FastCampas X Yanolja BootCamp

JavaScript 마스터 - Ch05 클래스

취업하고싶다! 2023. 7. 21. 18:41

5장에서는 클래스 개념에 대해 배웠다.

정리를 시작해보자

 

Ch05 - 클래스

prototype

배열 리터럴: 기호를 통해 생성

const fruits = ["apple", "banana", "cherry"]

기호를 사용한 리터럴 방식: [](배열데이터), {}(객체데이터), ''(문자데이터)

new 키워드로 실행하는 함수: 생성자 함수 - 인스턴스

length, includes - prototype 메소드(속성)

const fruits = new Array("apple", "banana", "cherry")
console.log(fruits)
console.log(fruits.length)
console.log(fruits.includes("banana"))  // true
console.log(fruits.includes("orange"))  // false


Array.prototype.heropy = function() {
    console.log(this)	// apple, banana, cherry
}

fruits.heropy()

// console.log(this)를 호출했을 때 "apple, banana, cherry"가 출력되는 이유
// JavaScript에서 배열(Array)의 프로토타입을 수정했기 때문
// Array.prototype.heropy는 배열 객체의 프로토타입에 heropy라는 새로운 메서드를 추가
// 이렇게 하면 배열을 포함한 모든 배열 객체에서 heropy 메서드를 사용할 수 있음
// 따라서 fruits.heropy()를 호출하면 fruits 배열이 this로 전달됨
// 따라서 console.log(this)를 실행하면 fruits 배열이 출력

const heropy = {
    firstName: 'heropy',
    lastName: 'Park',
    getFullName() {
        return `${this.firstName} ${this.lastName}`
    }
}

const neo = {
    firstName: 'neo',
    lastName: 'Anderson'
}

console.log(heropy.getFullName()) // heropy Park

// neo라는 객체데이터가 heropy의 getFullName을 호출
console.log(heropy.getFullName.call(neo))   // neo Anderson

 

동일한 작업을 하는 것이 여러 개가 있으면 굉장히 번거롭다.

이러한 단점을 보완한 것이 prototype!!!

function User(first, last) {
    this.firstName = first
    this.lastName = last
}

// 프로토타입의 메소드를 만들 때는 일반함수 사용(화살표 함수 사용하면 안됨)
// getFullName함수는 User라는 함수의 프로토타입으로 등록
User.prototype.getFullName = function() {
    return `${this.firstName} ${this.lastName}`
}

// User라는 생성자 함수에서 반환된 인스턴스 객체에서는 언제든지 프로토타입에 등록된 메소드를 사용할 수 있음!
const heropy2 = new User('Heropy', 'Park')
const neo2 = new User('Neo', 'Anderson')
console.log(heropy2)
console.log(neo2)
console.log(heropy2.getFullName())  
console.log(neo2.getFullName())

 

 

ES6 Classes

ES6 class란: 기존 프로토타입 기반 객체지향 프로그래밍보다 클래스 기반 언어에 익숙한 프로그래머가 보다 빠르게 학습할 수 있는 단순명료한 새로운 문법

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

const heropy = new User('Heropy', 'Park')
const neo = new User('Neo', 'Anderson')

console.log(heropy.getFullName())	// Heropy Park
console.log(neo.getFullName())		// Neo Anderson
console.log(heropy)			// User('Heropy', 'Park')
console.log(neo)			// User('Neo', 'Anderson')

 

 

Getter & Setter

위에서 썼던 User class를 조금 더 편하게 쓸 수 있음!

class User2 {
    constructor(first, last) {
        this.firstName = first
        this.lastName = last
    }
    // get이라는 키워드는 속성에 붙이지 않고 함수데이터(메소드)에 붙여서 사용
    // fullName은 값을 얻어내는 기능(fullName은 getter)
    // fullName이라는 메소드를 속성처럼 사용
    // 값을 조회하는 메소드
    get fullName() {
        return `${this.firstName} ${this.lastName}`
    }
    // fullName에 값을 할당해주는 메소드
    set fullName(value) {
        console.log(value)  // Neo Anderson
        ;[this.firstName, this.lastName] = value.split(' ')   // Neo Anderson이라는 문자데이터를 띄어쓰기 기준으로 쪼개서 배열데이터로 만듦
    }
} 

const heropy2 = new User2('Heropy', 'Park')
console.log(heropy2.fullName)   // Heropy Park

heropy2.firstName = 'Neo'
console.log(heropy2.fullName)   // Neo Park

heropy2.fullName = 'Neo Anderson' // 값을 할당
console.log(heropy2)    // Neo Anderson

=> heropy2를 const로 선언했는데 값을 재지정하는게 가능한가? 라고 생각했는데 검색해본 결과 된다고 한다.

<답변>

const로 선언된 변수는 재지정(재할당)이 불가능합니다.

하지만 주의해야 할 점은 const heropy2에서 heropy2가 가리키는 객체의 내용을 변경하는 것은 가능합니다.

예시에서 보는 것처럼 heropy2 객체의 firstName과 lastName 속성은 const로 선언되어 있지 않기 때문에 값을 변경할 수 있습니다. 따라서 heropy2.firstName = 'Neo'와 같이 해당 속성에 새로운 값을 할당할 수 있습니다.

또한, fullName은 get과 set 키워드를 사용하여 접근자 메서드로 정의되었습니다.

이러한 접근자 메서드를 통해 heropy2.fullName을 조회하거나 할당하는 것이 가능합니다.

접근자 메서드의 set 부분에서는 입력된 값을 이용하여 firstName과 lastName을 적절히 변경하는 작업을 수행합니다.

따라서 heropy2.fullName = 'Neo Anderson'라고 할 때, 이 값은 set fullName(value) 메서드의 value 매개변수로 전달되며,

이로 인해 firstName과 lastName이 적절히 변경되어 heropy2 객체의 내용이 업데이트되게 됩니다.

const로 선언된 변수 heropy2는 여전히 같은 객체를 가리키지만, 객체 내용의 변경은 가능합니다. 이것이 JavaScript에서 const 변수의 동작 방식입니다. 객체 내부의 속성을 변경하는 것은 변수가 가리키는 메모리 주소(참조)가 변경되는 것이 아니기 때문에 가능합니다. 하지만 새로운 객체로 다시 할당하는 것은 const 변수에 대해 재지정이기 때문에 허용되지 않습니다.

 

 

정적 메소드

클래스 자체에서 사용하고 싶으면 static 키워드 사용!!!(안붙이면 인스턴스에서만 사용 가능)

class User {
    constructor(first, last) {
        this.firstName = first
        this.lastName = last
    }
    getFullName() {
        return `${this.firstName} ${this.lastName}`
    }
    // User라는 클래스 자체에서 사용하고싶으면 static 키워드 사용(안붙이면 인스턴스에서만 사용 가능)
    // 정적메소드는 클래스에서만 사용 가능. 각각의 인스턴스에선 사용 불가
    static isUser(user) {
        if(user.firstName && user.lastName) {
            return true
        } 
        return false
    }
} 

const heropy = new User('Heropy', 'Park')
const neo = new User('Neo', "Anderson")
const lewis = {
    name: "Lewis Yang",
    age: 85
}

console.log(heropy.getFullName())
console.log(neo.getFullName())
// console.log(User.getFullName()) // TypeError 
// console.log(heropy.isUser())    // TypeError

console.log(User.isUser(heropy))    // true
console.log(User.isUser(neo))       // true
console.log(User.isUser(lewis))     // false


// cf) Array.isArray() - 인자가 Array인지 판별

 

 

상속과 instanceof

다른 클래스를 상속받는 클래스는 constructor {} 부분에 super() 반드시 필요

super() 안에 들어가는 내용은 상속받은 클래스의 constructor()부분의 인자가 들어감

 

instanceof - 키워드 앞쪽의 데이터가 키워드 뒤쪽의 클래스에서 인스턴스로 만들어졌는지 확인

// 상속

// 운송수단
class Vehicle {
    constructor(acceleration = 1) {
        this.speed = 0
        this.acceleration = acceleration
    }
    accelerate() {
        this.speed += this.acceleration
    }
    decelerate() {
        if (this.speed <= 0) {
            console.log("정지!")
            return
        }
        this.speed -= this.acceleration
    }
}

// 자전거
// super함수 - vehicle 클래스가 가진 constructor 부분
// bicycle 클래스는 운송수단을 상속받아서 내부에서 사용할건데, 운송수단의 constructor가 super을 통해 호출
class Bicycle extends Vehicle {
    constructor(price=100, acceleration) {
        super(acceleration)
        this.price = price
        this.wheel = 2
    }
}

// new Bicycle() - 생성자 함수
// bicycle - 인스턴스
const bicycle = new Bicycle(300, 2) 
console.log(bicycle)

const bicycle2 = new Bicycle(300) 
bicycle2.accelerate()
console.log(bicycle2) // speed = 1
bicycle2.accelerate()
console.log(bicycle2) // speed = 2


// instanceof 키워드 - 키워드 앞쪽의 데이터가 키워드 뒤쪽의 클래스에서 인스턴스로 만들어졌는지 확인
console.log(bicycle2 instanceof Bicycle)    // true
console.log(bicycle2 instanceof Vehicle)    // true - Bicycle 클래스가 Vehicle 클래스를 상속받아서 만들어졌으므로 true


class Car extends Bicycle {
    constructor(license, price, acceleration) {
        super(price, acceleration)
        this.license = license
        this.wheel = 4
    }
    // accelerate 메소드 재정의 
    accelerate() {
        if (!this.license) {
            console.error('무면허!')
            return
        }
        this.speed += this.acceleration
        console.log('가속!', this.speed)
    }
}

const carA = new Car(true, 7000, 10)
const carB = new Car(false, 4000, 6)

carA.accelerate()
console.log(carA)   // speed = 10

carB.accelerate()
console.log(carB)   // 무면허!

console.log(carA instanceof Car)    // true


// 보트
class Boat extends Vehicle {
    constructor(price,  acceleration) {
        super(acceleration)
        this.price = price
        this.moter = 1
    }
}

const boat = new Boat(10000, 5)
console.log(boat)
console.log(boat instanceof Bicycle)    // false

 

 

instanceof와 constructor

// 상속 예제
class A {
    constructor() {
    }
}

class B extends A {
    constructor() {
        super()
    }
}

class C extends B {
    constructor() {
        super()
    }
}

const a = new A()
const b = new B()
const c = new C()

console.log(c instanceof A) // true
console.log(c instanceof B) // true
console.log(c instanceof C) // true

// constructor 속성 사용
console.log(c.constructor === A)    // false
console.log(c.constructor === B)    // false
console.log(c.constructor === C)    // true

const fruits = new Array('apple', 'banana')
console.log(fruits.constructor === Array)   // true
console.log(fruits instanceof Array)        // true

 

5장을 복습하면서, getter & setter 부분에서 궁금한 점이 생겼다.

heropy2를 const로 선언했는데 값을 재지정하는게 가능한건가라고 생각했고 검색해본 결과 다음과 같은 답변을 받았다.

<답변>

const로 선언된 변수는 재지정(재할당)이 불가능합니다.

하지만 주의해야 할 점은 const heropy2에서 heropy2가 가리키는 객체의 내용을 변경하는 것은 가능합니다.

예시에서 보는 것처럼 heropy2 객체의 firstName과 lastName 속성은 const로 선언되어 있지 않기 때문에 값을 변경할 수 있습니다. 따라서 heropy2.firstName = 'Neo'와 같이 해당 속성에 새로운 값을 할당할 수 있습니다.

또한, fullName은 get과 set 키워드를 사용하여 접근자 메서드로 정의되었습니다.

이러한 접근자 메서드를 통해 heropy2.fullName을 조회하거나 할당하는 것이 가능합니다.

접근자 메서드의 set 부분에서는 입력된 값을 이용하여 firstName과 lastName을 적절히 변경하는 작업을 수행합니다.

따라서 heropy2.fullName = 'Neo Anderson'라고 할 때, 이 값은 set fullName(value) 메서드의 value 매개변수로 전달되며,

이로 인해 firstName과 lastName이 적절히 변경되어 heropy2 객체의 내용이 업데이트되게 됩니다.

const로 선언된 변수 heropy2는 여전히 같은 객체를 가리키지만, 객체 내용의 변경은 가능합니다. 이것이 JavaScript에서 const 변수의 동작 방식입니다. 객체 내부의 속성을 변경하는 것은 변수가 가리키는 메모리 주소(참조)가 변경되는 것이 아니기 때문에 가능합니다. 하지만 새로운 객체로 다시 할당하는 것은 const 변수에 대해 재지정이기 때문에 허용되지 않습니다.

 

확실하게 이해한 것은 아니지만, 무슨 느낌인지 이해했다. 근데 그러면 heropy2 변수를 const로 선언한 이유가 있는건가? 라는 생각이 들었다. heropy2가 가리키는 객체의 내용을 변경할 수 있다고하는데 heropy2 객체가 가진 firstName, lastName 속성을 둘 다 바꿔주면 결국 heropy2 자체가 아예 바뀌는 것인데 왜 const로 선언했는지 모르겠다.

 

그래서 다시 검색해보았는데, 그냥 let과 const의 차이는 재할당 가능 여부라고 한다.

let heropy2 = new User2('Heropy', 'Park');

heropy2.firstName = 'Neo'; // 객체 내부의 속성 변경: 가능
heropy2.lastName = 'Anderson'; // 객체 내부의 속성 변경: 가능

heropy2 = new User2('Another', 'Person'); // 가능: let으로 선언했으므로 재할당 가능
const heropy2 = new User2('Heropy', 'Park');

heropy2.firstName = 'Neo'; // 객체 내부의 속성 변경: 가능
heropy2.lastName = 'Anderson'; // 객체 내부의 속성 변경: 가능

heropy2 = new User2('Another', 'Person'); // 에러 발생: const로 선언했으므로 재할당 불가능

let으로 선언된 heropy2 변수는 다른 객체로 완전히 재할당이 가능하며, const로 선언된 heropy2 변수는 재할당이 불가능하다.

그러나 객체 내부의 속성들은 둘 다 변경이 가능하다.

즉, const와 let의 차이점은 변수에 새로운 객체를 할당하는 것이지 변수가 가리키는 객체 내부의 속성을 변경하는 것과는 관련이 없다고 한다. 

 

개념이 많이 헷갈렸는데 답변을 보고 어느정도 이해가 되었다.

블로그를 정리하면서 개념 복습이 나름 잘 되고 있는 것 같은데 정리하고 보지 않으면 까먹을 것 같기에, 꾸준히 방문하면서 복습해 개념을 까먹지 않도록 노력해야겠다.