[learning javascript] chapter 9. 객체와 객체지향 프로그래밍

객체와 객체지향 프로그래밍

프로퍼티 나열

for…in

for(let prop in o) { if(!o.hasOwnProperty(prop)) continue; console.log(${prop}: ${o[prop]}); }

- for...in 루프에는 키가 심볼인 프로퍼티는 포함되지 않음


#### Object.keys
- 객체에서 나열 가능한 문자열 프로퍼티를 배열로 반환
```javascript
cosnt SYM = Symbol();
const o = { a: 1, b: 2, c: 3, [SYM]: 4 };

Object.keys(o).forEach(prop => console.log(`${prop}: ${o[prop]}`));

Object.keys(o) .filter(prop => prop.match(/^x/)) .forEach(prop => console.log(${prop}: ${o[prop]}));


## 객체 지향 프로그래밍
- 객체는 데이터와 기능을 논리적으로 묶어 놓은 것
- 클래스는 어떤 자동차처럼 추상적이고 범용적인 것
- 인스턴스는 특정 자동차처럼 구체적이고 한정적인 것
- 기능은 메서드
  
#### 클래스와 인스턴스 생성
- ES6 이전에 자바스크립트에서 클래스를 만드는 건 직관적이지도 않고 무척 번거로운 일이었음
- ES6에서는 클래스를 만드는 간편한 새 문법을 도입
```javascript
class Car {
    constructor() {

    }
}
class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
        this.userGears = ['P', 'N', 'R', 'D'];
        this.userGear = this.userGears[0];
    }
    shift(gear) {
        if(this.userGears.indexOf(gear) < 0)
            throw new Error(`Invalid gear: ${gear}`);
        this.userGear = gear;
    }
}

클래스는 함수다

프로토타입

car1.shift = function(gear) { this.userGear = gear.toUpperCase(); } car1.shift === Car.prototype.shift; // false car1.shift === car2.shift; // false car1.shift(‘d’); car1.userGear; // ‘D’

- car1객체는 shift 메서드가 없지만, car1.shift('D')를 호출하면 car1의 프로토타입에서 메서드를 검색함
- car1에 shift 메서드를 추가하면 car1과 프로토타입에 같은 이름의 메서드가 존재하게 됨
- 이후에 car1.shift('d')를 호출하면 car1의 메서드가 호출되면서 프로토타입의 메서드는 무시됨

#### 정적 메서드
- 인스턴스 메서드 외에도 정적 메서드(클래스 메서드)가 있음
- 이 메서드는 특정 인스턴스에 적용되지 않음
- 정적메서드에서 this는 인스턴스가 아니라 클래스에 묶임
- 일반적으로 정적 메서드에는 this대신 클래스 이름을 사용하는 것이 좋은 습관임
- 정적 메서드는 클래스와 관련되지만 인스턴스와 관련이 없는 작업에 사용
```javascript
class Car {
    static getNextVin() {
        return Car.nextVin++;   // this.nextVin++라고 써도 되지만
                                //  Car를 앞에 쓰면 정적메서드라는 점을 인식시켜줌
    }
    constructor(make, model) {
        this.make = make;
        this.model = model;
        this.vin = Car.getNextVin();
    }
    static areSimilar(car1, car2) {
        return car1.make === car2.make && car1.model === car2.model;
    }
    static areSame(car1, car2) {
        return car1.vin === car2.vin;
    }
}
Car.nextVin = 0;

const car1 = new Car("Tesla", "S");
const car2 = new Car("Mazda", "3");
const car3 = new Car("Mazda", "3");

car1.vin;       // 0
car2.vin;       // 1
car3.vin;       // 2

Car.areSimilar(car1, car2); // false
Car.areSimilar(car2, car3); // true
Car.areSame(car2, car3);    // false
Car.areSame(car2, car2);    // true

상속

class Car extends Vehicle { constructor() { super(); console.log(“Car created”); } deployAirbags() { console.log(“BWOOSH!”); } }

- `super()`는 슈퍼클래스의 생성자를 호출
- 서브클래스에서 이 함수를 호출하지 않으면 에러 발생
```javascript
const v = new Vehicle();
v.addPassenger("Frank");
v.addPassenger("Judy");
v.passengers;               // ["Frank", "Judy"]
const c = new Car();
c.addPassenger("Alice");
c.addPassenger("Cameron");
c.passengers;               // ["Alice", "Cameron"]
v.deployAirbags();          // error
c.deployAirbags();          // "BWOOSH!"

다형성

객체 프로퍼티 나열 다시 보기

// 유효하지만, 권장하지 않음 Super.prototype.sneaky = ‘not recommended!’;

class Sub extends Super { constructor() { super(); this.name = ‘Sub’; this.isSub = true; } } const obj = new Sub();

for(let p in obj) { console.log(${p}: ${obj[p]} + (obj.hasOwnProperty(p)?’’:’(inherited)’)); }

- 위 프로그램을 실행한 결과

name: Sub isSuper: true isSub: true sneaky: not recommended! (inherited)

- name, isSuper, isSub 프로퍼티는 모두 프로토타입 체인이 아니라 인스턴스에 정의
- 슈퍼클래스 생성자에서 선언한 프로퍼티는 서브클래스 인스턴에도 정의됨
- Object.keys를 사용하면 프로토타입 체인에 정의된 프로퍼티를 나열하는 문제를 피할 수 있음

#### 문자열 표현
- 객체의 문자열 표현 : toString()
- toString()의 기본 동작은 "[object Object]"인데 이건 거의 쓸모가 없음
```javascript
class Car {
    toString() {
        return `${this.make} ${this.model}: ${this.vin}`;
    }
}

다중 상속, 믹스인, 인터페이스