JavaScript - Class & Closure

윤수빈·2024년 8월 16일
0

1. Class

클래스는 객체를 생성하기 위한 설계도이다.
인스턴스는 클래스를 실체화한 객체이다.

클래스 선언 및 인스턴스화

class Person {
    // 생성자 함수에 해당 객체를 생성하기 위한 프로퍼티를 만들어낸다.
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name}!`);
    }

    myAge() {
        console.log(`I'm ${this.age} years old.`);
    }
}

// new 키워드를 통해 클래스 객체를 생성
const person = new Person('John', 21);
console.log(person);

이렇게 하면 person이라는 Person 클래스의 인스턴스가 하나 생성되었다.

인스턴스 호출 및 속성 변경

person.greet();					=> person의 greet() 메서드 호출
console.log(person.name);		=> person의 name 값을 출력
person.name = 'Khan'			=> person의 name 값을 'Khan'으로 변경

2. Getters and Setters

클래스를 선언하고 각종 프로퍼티를 정의하는데 해당 프로퍼티에 접근하여 조작하는 방법 중 하나이다.

직접 접근을 해도 되지만 Getter / Setter 를 사용하면 더 안전하고 간편하게 세팅할 수 있다.

안전한 이유는 외부에서 해당 인스턴스의 속성에 직접 접근하는 방식이 아닌 get, set 메서드를 통해 접근할 수 있기 때문이다.

private을 관리하는 메서드라고 생각하면 된다.

특히, Setter의 경우 검증하는 로직도 메서드안에 구현할 수 있어 용이하다.

get과 set 사용 예시

class Rectangle {
    constructor(width, height) {
        // _ : underscore, 이것은 은밀하게(private) 감춰야 할 때 사용하는 것 (getters와 setters 를 사용할 때 필요)
        this._width = width;
        this._height = height;
    }

    // width 를 위한 getter
    get width() {
        return this._width;
    }
    // width 를 위한 setter
    set width(value) {
        if(value <= 0) {
            console.log('0보다 커야합니다.');

        } else if (typeof value !== number) {
            console.log('숫자타입이 아닙니다.');
        }
        else
        this._width = value
    }
    // height 를 위한 getter
    get height() {
        return this._height;
    }
    // height 를 위한 setter
    set height(value) {
        if(value <= 0) {
            console.log('0보다 커야합니다.');

        } else if (typeof value !== 'number') {
            console.log('숫자타입이 아닙니다.');
        }
        else
        this._height = value
    }
}

3. inheritance (상속)

클래스는 추가로 상속이라는 기능을 가지고 있다.
상속을 하기 위해서는 extends 라는 키워드를 사용하며 클래스를 선언하는 부분에서 사용한다.

클래스는 부모클래스와 자식클래스를 나눌 수 있으며,
자식클래스도 가지를 뻗어 어떠한 클래스의 부모가 될 수 있다.

상속을 받게되면 부모 클래스의 속성을 그대로 물려받는다.

상속 예시 및 메서드 오버라이딩

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

    speak () {
        console.log(`${this.name} says!`);
    }
    
    bark () {
    	console.log(`${this.name} baaaaaaaaark!`);
    }
}

// extends 키워드로 Animal 클래스의 속성을 Dog에게 부여
class Dog extends Animal {
    
    // Animal 클래스의 bark 메서드를 재정의
    // overriding 이라고 함.
    bark () {
        console.log(`${this.name} bark!`);
    }
}

const dog = new Dog("Max");
dog.speak();					=> Max says! 출력
dog.bark();						=> Max bark! 출력

부모 클래스의 speak() 메서드를 사용하는 것은 물론이고,
부모 클래스의 bark() 메서드를 오버라이딩하여 재정의도 가능하다.

자식 클래스 속성 변경 혹은 추가

또한, 부모 클래스의
1. 속성 일부만 받고 싶거나
2. 속성을 추가하고 싶을 때
아래 방법으로 할 수 있다.

class Car {
    constructor(modelName, modelYear, type, price) {
        this.modelName = modelName;
        this.modelYear = modelYear;
        this.type = type;
        this.price = price;
    }
}

class ElectronicCar extends Car{
    // 1. 생성자 재정의가 필요하다면 constructor 사용
    // 2. 여기선 모델 타입이 전기이기 때문에 타입을 따로 지정하지 않도록 함.
    constructor(modelName, modelYear, price, chargeTime) {
        // 3. Car(부모 class)에게도 알려주어야 함.
        super(modelName, modelYear, 'e', price);
        this._chargeTime = chargeTime;
    }
    
    get chargeTime() {
        return this._chargeTime;
    }
    set chargeTime(value) {
        if(value >= 0 && value <= 24) {
            this._chargeTime = value;
        } else {
            console.log('Charge time should be between 0 and 24 hours.');
            return;
        }
    }

    showChargeTime() {
        console.log(`Charge time: ${this._chargeTime} hours`);
    }
}

4. Static Method (정적메서드)

선언한 클래스의 인스턴스를 만들고, 인스턴스의 메서드에 접근하여 호출하는 방식이 아니다.

인스턴스를 만들지 않고도 객체들을 묶어서 관리할 수 있는데 이러한 것을 정적 메서드라고 한다.

class Calculator {
    static add(a,b) {
        console.log(a+b);
    }
}

Calculator.add(3, 5);

위 내용처럼 Calculator 라는 클래스안에 static add() 메서드를 선언하면,
인스턴스를 생성하지않고도 Calculator 클래스의 메서드에 접근하여 호출이 가능하다.

비슷한 특징을 가진 메서드를 객체 단위로 묶어서 재사용할 수 있다는 점이 장점이다.


5. closure (클로저)

함수와 함수가 선언된 LE 환경과의 조합...

처음 들었을 때 바로 이해되지 않았다. 이게 무슨뜻이지??

콜스택과 컨텍스트를 다루는 페이지에서 VE와 LE, 그리고 LE에는 record와 호이스팅, outer라는 환경 정보를 배운적이 있었다.

여기서 outer(환경 정보)의 경우 체인 스코프라는 기능이 있어 안쪽에서 바깥쪽까지 검색해나가는 행위를 배운 적이 있었다.

이 부분에서 클로저의 기능이 나타난다.

일단, JS 엔진은 스크립트가 작동하면 콜스택이라는 곳에 실행할 코드들이 담기게 된다.
그렇게 쌓인 스택들은 코드가 종료가되면 콜스택에서 pop이 되며 소멸이되는데, 클로저의 경우 소멸이 되어도 해당 스택(스코프)에 있던 변수가 소멸이 되지 않고 유지되는 것을 의미한다.

클로저의 예시

const z=1;

function outerFunc3() {
    const z = 10;
    const inner = function() {
        console.log(z);
    }
    return inner;
}

// 1. innerFunc3에 outerFunc3의 결과를 담는다.
const innerFunc3 = outerFunc3();

// 2. 저장된 innerFunc3을 호출한다. => 여전히 10이 출력된다.
innerFunc3();

위에 상황을 정리하면 callstack은 -> (71줄) 전역context -> (72줄) 전역context | outerFunc3 -> (73줄) 전역context 처럼 outerFunc3가 호출되고 innerFunc3에 return 값을 할당해주면 outerFunc3는 콜스택에서 사라지게된다.
그리고 (75줄) 전역context | innerFunc3() 이 호출되는데

이 innerFunc3은 outerFunc3 객체의 inner 라는 객체 아닌가???

즉, 현재 콜스택은 전역context | innerFunc3() | inner 가 되는 것이다.

여기서 inner의 스코프는 어디를 바라보는가?
분명 outerFunc3은 콜스택에서 종료되어 사라졌다. 가장 가까운 실행 컨텍스트는 innerFunc3이고 innerFunc3은 전역 객체로써 선언되어 있다.
그럼에도 z는 전역 변수인 1이 아니라 outerFunc() 변수인 10을 참조하고 있다 것이다.

이러한 이유는 위에서 정리했듯이
1. 외부 함수인 outer 보다 중첩 함수인 inner가 innerFunc3으로 저장되어 있고,
2. 중첩함수 inner를 return 해주며,
3. (75줄) innerFunc3()으로 외부에서 또 호출이 되기 때문에
4. 소멸하지 않고 유지되는 것이다.

이것이 바로 클로저의 기능이다.

클로저 활용

즉, 중첩함수가 외부함수보다 더 오래 유지되는 경우 중첩함수가 종료가 되어도 여전히 유지되는 속성을 의미한다.

때문에 클로저는 안전하게 변경하고 유지하는 속성에서 사용된다.

인게임에서 예를 들면 캐릭터의 상태정보가 생각이 난다.

전역 변수, 외부에서 접근이 가능한 변수가 아닌 은닉된 변수라는 점이 강력하다고 생각이 들고, 반드시 알고 있어야 하는 것이라고 생각이 들었다.


6. 깃헙 링크

JavaScript-Essential Week 05

profile
정의로운 사회운동가

0개의 댓글