관리 메뉴

bright jazz music

자바스크립트 클래스 본문

Language/Javascript

자바스크립트 클래스

bright jazz music 2024. 1. 1. 17:14

자바스크립트의 클래스

 

1. 클래스 예시

//Car class 선언
class Car {
	// constructor는 생성자. 최초에 받을 인수를 결정 가능하며, 객체 초기화 용도로 사용된다.
    constructor(name) {
        this.name = name
    }

    // 메서드
    honk() {
        console.log(`${this.name}이 경적을 울립니다.`)
    }

    // 정적 메서드
    static hello() {
        console.log('저는 자동차입니다.')
    }

    // setter
    set setAge(value) {
        this.carAge = value
    }

    // getter
    get getAge() {
        return this.age
    }

}

// Car 클래스를 이용해 car 객체 만들기
const myCar = new Car('자동차')

// 메서드 호출
myCar.honk()

// 정적 메서드 호출: 클래스에서 직접 호출
Car.hello()


// 정적 메서드는 클래스로 만든 객체에서는 호출할 수 없다.
// Uncaught TypeError: myCar.hello is not a function
myCar.hello()

// setter로 값 할당 가능
myCar.setAge = 32

// getter로 값을 가져올 수 있다.
console.log(myCar.getAge, myCar.name) // 32 자동차

 

 

2. constructor

: 생성자는 객체를 생성하는 데 사용하는 특수한 메서드이다. 하나만 존재할 수 있으며 여러 개 사용하면 에러가 발생한다.

생성자서 수행할 작업이 없으면 생략 가능하다.

 

class Car {

	constructor() {
        //생략가능
    }

	//두 개는 오류남
    constructor(name) {
        this.name = name
    }
}

 

 

3. 프로퍼티

프로퍼티란 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값을 의미한다.

class Car {

	// 값을 받으면 내부에 프로퍼티로 할당된다
    constructor(name) {
        this.name = name
    }
}

 

기본적으로 인스턴스 생성 시 constructor 내부에는 빈 객체가 할당되어 있다. 이 빈 객체에 프로퍼티의 key와 value를 넣어서 값을 활용할 수 있다. JS는 타 언어처럼 접근 제한자가 완벽히 지원되지는 않는다. 그러나 ES2019에 #을 붙여서 private을 선언하는 방법이 추가되었다. 또한 타입스크립트에서는 private, protected, public을 사용할 수 있다. 하지만 기본적으로 JS에서는 모든 프로퍼티가 public 상태이다. 과거에는 프로퍼티명 앞에 _를 붙여서 private를 표현하기는 했지만 컨벤션일 뿐 기능적으로 동작하는 것은 아니다.

 

4. getter / setter

class Car {

    constructor(name) {
        this.name = name
    }
    
    get firstCharacter() {
    	return this.name[0]
    }
    
    set firstCharacter(char) {
     //slice : 시작 인덱스부터 종료 인덱스까지 값을 복사하여 반환. 여기서는 1번 인덱스부터 끝까지
    	this.name = [char, ...this.name.slice(1)].join('')
    }
}

const myCar = new Car('자동차')

//게터
myCar.firstCharacter // 자

// 세터
myCar.firstCharacter = '차'
//myCar.firstCharacter('차') 이렇게 쓸 수는 없다.

console.log(myCar.firstCharacter, myCar.name) //차, 자동차

 

 

5. 인스턴스 메서드( 또는 prototype method)

클래스 내부에서 선언한 메서드를 인스턴스 메서드라고 한다. 이 인스턴스 메서드는 실제로 JS의 prototype에 선언되므로 프로토타입 메서드로 불리기도 한다.

 

prototype에 선언된다는 것이 무슨 의미일까?

class Car {

    constructor(name) {
        this.name = name
    }
    
    // 인스턴스 메서드 정의
    hello() {
        console.log(`안녕하세요, ${this.name}입니다.`)
    }
}

const myCar = new Car('자동차')
myCar.hello() // 안녕하세요, 자동차입니다.

 

위와 같이 생성한 객체에서 클래스에 접근할 수 있다. 이렇게 접근할 수 있는 이유는 이 메서드가 prototype에 선언됐기 때문이다.

 

const myCar = new Car('자동차')
Object.getPrototype(myCar) // {constructor: f, hello: f}

 

Object.getPrototypeOf를 사용하면 인수로 넘겨준 변수의 prototype을 확인할 수 있다. 이에 대한 결과로 {constructor: f, hello:f}를 반환받아 Car의 prototype을 받은 것으로 짐작할 수 있다. 아래와 같은 코드로 더욱 확실하게 알아볼 수 있다.

Object.getPrototypeOf(myCar) === Car.prototype // true

 

__proto__ 또한  Object.getProtoTypeOf() 와 동일하게 작동한다

myCar.__proto__ === Car.prototype // true

그러나 이는 과거 브라우저와의 호환성 때문에 남아있는 것이므로 가급적 사용하지 않는 게 좋다. Object.getPrototypeOf를 사용하자.

 

직접 객체에 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주는 것을 프로토타입 체이닝이라고 한다. 모든 객체는 프로토타입을 가지고 있다. 특정 속성을 찾을 때 자기 자신부터 시작해서 이 프로토타입을 찾고 최상위 객체인  Object까지 훑는다. 

 

이 경우, myCar에서 시작해서 부모인 Car에서 hello를 찾는 프로토타입 체이닝을 거쳐 비로소 hello를 호출한다. toString이 이와 비슷한 원리이다. toString은 객체 어디에서도 선언하는 경우가 없지만 대부분의 객체에서 모두 사용할 수 있다. 이는 toString도 마찬가지로 프로토타입 체이닝을 거쳐 Object에 있는 toString을 만나기 때문이다.

 

결론적으로 이 프로토타입과 프로토타입 체이닝이라는 특성 때문에 생성한 객체에서 직접 선언하지 않은, 클래스에 선언한 hello() 메서드를 호출할 수 있고, 이 메서드 내부에서 this도 접근해 사용할 수 있게 된다.

 

프로토타입을 사용하는 이유는 아래와 같다.

 

프로토타입은 JavaScript의 중요한 특징 중 하나로, 객체 지향 프로그래밍의 상속을 구현하는 메커니즘이다. 프로토타입은 다음과 같은 이유로 사용된다:

1. **재사용 가능한 속성 및 메서드**: 프로토타입을 사용하면 여러 객체 간에 함수나 속성을 공유할 수 있다. 이는 메모리를 효율적으로 사용하고, 중복 코드를 줄이는 데 도움이 된다.

2. **상속**: JavaScript에서는 프로토타입 체인을 통해 상속을 구현한다. 한 객체의 프로토타입은 다른 객체가 될 수 있으며, 이를 통해 프로토타입 체인 상의 상위 객체의 속성과 메서드를 하위 객체에서 사용할 수 있다.

3. **동적 확장**: 객체의 프로토타입은 런타임에 수정될 수 있다. 이는 객체 또는 클래스의 동작을 동적으로 변경하거나 확장하는 데 유용하다.

4. **효율적인 메모리 관리**: 프로토타입을 통해 정의된 메서드는 객체의 인스턴스마다 복사되지 않는다. 모든 인스턴스는 같은 프로토타입의 메서드에 접근할 수 있으므로, 같은 함수에 대한 여러 복사본을 생성할 필요가 없어 메모리 사용이 효율적이다.

프로토타입을 이해하고 사용하는 것은 JavaScript에서 효율적이고 강력한 코드를 작성하는 데 중요하다. 클래스 기반 언어와는 다른 접근 방식이지만, JavaScript의 프로토타입 상속은 유연하고 강력한 객체 지향 프로그래밍을 가능하게 한다.

 

 

myCar에서 hello()를 사용하는 과정은 아래와 같다.

JavaScript에서 `myCar`와 같은 객체가 메서드나 속성에 접근할 때, 프로토타입 체인을 따라가며 해당 메서드나 속성을 찾는다. 그러나 일단 해당 메서드나 속성을 찾게 되면, 더 이상 프로토타입 체인을 따라 올라가지 않는다.

구체적으로, `myCar.hello()` 호출 시 시나리오는 다음과 같다:

1. **myCar에서 hello 검색**: 먼저 `myCar` 객체 자체에서 `hello` 메서드를 찾는다.
2. **Car.prototype에서 hello 검색**: `myCar` 객체에 `hello` 메서드가 없다면, JavaScript는 `myCar`의 프로토타입인 `Car.prototype`에서 `hello` 메서드를 찾는다.
3. **hello 메서드 찾음**: `Car.prototype`에 `hello` 메서드가 정의되어 있으므로, 이 메서드를 사용한다.
4. **프로토타입 체인 종료**: 메서드를 찾았으므로, 더 이상 체인을 따라 올라가지 않는다. 즉, `Object.prototype`까지 올라가서 `hello` 메서드를 찾지 않는다.

만약 `Car.prototype`에도 `hello` 메서드가 없다면, JavaScript는 프로토타입 체인을 따라 계속 올라가 `Object.prototype`에서 해당 메서드나 속성을 찾는다. 그러나 일반적으로 `Object.prototype`에는 특정 클래스나 객체에 특화된 메서드가 정의되어 있지 않다. 따라서 `Object.prototype`에 `hello` 메서드가 있을 가능성은 매우 낮으며, 대부분의 경우 프로토타입 체인은 필요한 메서드나 속성을 찾은 시점에서 중단된다.

 

그러면 myCar는 자신만의 myCar.prototype을 가지고 있을까? 그렇지 않다.

 

JavaScript에서 일반적인 객체(예: `myCar`와 같이 클래스의 인스턴스로 생성된 객체)는 자체적으로 별도의 `.prototype` 속성을 가지고 있지 않다. 대신, 이러한 객체는 생성자 함수의 `prototype` 속성에 의해 정의된 프로토타입을 상속받는다. 이 경우, `myCar` 객체는 `Car.prototype`을 상속받는다.

자세히 설명하자면:

1. **Car.prototype**: `Car` 클래스(또는 생성자 함수)는 `Car.prototype`이라는 프로토타입 객체를 가진다. 이 객체는 `Car` 클래스로부터 생성된 모든 인스턴스가 공유하는 속성과 메서드를 포함한다. 

2. **myCar의 프로토타입**: `myCar` 인스턴스는 `Car.prototype`에 정의된 속성과 메서드에 접근할 수 있다. 하지만 `myCar` 자체에는 별도의 `.prototype` 속성이 없다. 대신, `myCar`는 내부 프로토타입 링크(`[[Prototype]]`, 또는 JavaScript에서 `__proto__` 속성으로 접근 가능)를 통해 `Car.prototype`과 연결된다.

3. **Object.prototype**: JavaScript에서 모든 객체는 기본적으로 `Object.prototype`에서 메서드와 속성을 상속받는다. `Car.prototype` 역시 `Object.prototype`을 상속받으므로, `myCar`는 `Object.prototype`의 메서드와 속성에도 접근할 수 있다.

따라서 `myCar`는 `Car.prototype`을 상속받으며, 이를 통해 `Car` 클래스의 공유 메서드 및 속성에 접근할 수 있다. 그러나 `myCar` 객체 자체에는 별도의 `.prototype` 속성이 존재하지 않는다. 클래스(또는 생성자 함수)에만 `.prototype` 속성이 존재하며, 인스턴스는 해당 클래스의 `.prototype`을 상속받아 사용한다.

 

 

 

6. 정적 메서드(static method)

정적 메서드는 클래스명으로 호출할 수 있는 메서드다.

class Car {
	static hello() {
    		console.log('안녕하세요')
    }
}

const myCar = new Car()

myCar.hello() // TypeError: myCar.hello is not a function
Car.hello() // 안녕하세요

 

정적 메서드 내부의 this는 클래스로 생성된 인스턴스가 아닌 클래스 자신을 가리키지 때문에 다른 메서드에서 일반적으로 사용하는 this를 사용할 수 없다.

 

정적 메서드는 비록 this에 접근할 수 없지만 인스턴스를 생성하지 않아도 사용할 수 있다는 점, 그리고 생성하지 않아도 접근할 수 있기 때문에 객체를 생성하지 않더라도 여러 곳에서 재사용이 가능하다는 장점이 있다. 이 때문에 애플리케이션 전역에서 사용하는 유틸 함수를 정적 메서드로 많이 활용한다.

 

7. 상속

class Car {
	constructor(name) {
    	this.name = name
    }
    
    honk() {
    	console.log(`${this.name}이 경적을 울린다.`)
    }
}

class Truck extends Car {
	constructor(name) {
    	//부모클래스의 constructor, 즉 Car의 constructor를 호출한다.
        //자기가 받은 이름을 부모에게 넘겨주고 부모도 초기화한다.
        super(name)
    }
    
    load() {
    	console.log('짐을 싣습니다.')
    }
}

const myCar = new Car('자동차')
myCar.honk() // 자동차가 경적을 울린다.

const truck = new Truck('트럭')
truck.honk() //트럭이 경적을 울린다.
truck.load() // 짐을 싣습니다.

 

8. 클래스와 함수와의 관계

 

클래스는 ES6에서 나온 개념으로 이전에는 프로토타입을 활용해 클래스의 작동방식과 동일하게 구현할 수 있었다. 반대로 말하면 자바스크립트에서 클래스는 결국 프로토타입을 기반으로 작동하 것이다.

 

즉 클래스는 객체지향 언어를 사용하던 다른 프로그래머가 JS에 좀 더 접근하기 쉽게 만들어주는 문법적 설탕(syntactic sugar)의 역할을 한다고 볼 수 있다.

 

 

 

참고:  리액트 Deep Dive

'Language > Javascript' 카테고리의 다른 글

자바스크립트 기본 문법  (0) 2024.02.26
이벤트 루프와 비동기 통신  (0) 2024.01.03
클로저(closure)  (0) 2024.01.03
함수 관련  (0) 2023.12.31
자바스크립트의 타입과 동등비교  (1) 2023.12.29
Comments