클래스 Class

김수정·2020년 4월 10일
0

생성자 함수 (constructor function)

여러 개의 유사한 객체를 만들 때 필요합니다.

일반 함수와 생성자 함수의 기술적인 차이가 원래 없었어서 구분하기 위해 함수 이름의 첫 글자를 대문자로 하는 관례와 실행할 때 new 키워드를 붙이는 점만 다릅니다. 이런 실수를 방지하기 위해 생성자 함수를 일반 함수처럼 호출할 때 에러를 발생시키도록 로직을 구성할 수도 있습니다.

new.target
이 프로퍼티는 생성자 함수로 호출하면 함수 자체를 반환하고, 일반 함수로 호출하면 undefined를 반환합니다.
이 점을 활용해 생성자 함수로 호출하지 않으면 에러를 보내줄 수 있습니다.

function Person (name) {
  if (new.target === undefined) {
    throw new Error('new 연산자를 사용하세요.')
  }
  // ...
}

또한, 추상 클래스로 활용이 가능합니다.

class A {
  constructor() {
    if (new.target === A) { throw new Error('추상클래스입니다'); }
  }
}

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

const b = new B(); 
const a = new A(); // error

return문
보통 생성자 함수에 return을 쓰지 않지만, 그럴 경우를 가정한다면 아래와 같이 동작합니다.
return문이 없는 경우: this를 리턴합니다.
객체를 리턴하는 경우: 객체가 리턴됩니다.
원시형을 리턴하는 경우: 리턴문이 무시됩니다.

prototype 상속

기존의 기능을 확장할 경우에 사용합니다.

[[Prototype]]

자바스크립트 객체의 숨김 프로퍼티입니다. 해당 객체가 상속 받은 객체를 나타냅니다.
이 프로퍼티는 null 혹은 다른 객체에 대한 참조값을 갖는데 참조 대상을 프로토타입이라고 부릅니다.
object에서 property를 읽을 때 해당 object에 프로퍼티가 없다면 자동으로 프로토타입에서 프로퍼티를 찾습니다.

__proto__

__proto__는 [[Prototype]]의 getter, setter입니다.
요즘에는 __proto__ 대신 Object.getPrototypeOf(), Object.setPrototypeOf()를 사용합니다.

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// 프로퍼티 eats과 jumps를 rabbit에서도 사용할 수 있게 되었습니다.
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

프로토타입 체이닝

상속을 체인처럼 프로토타입을 연결하여 구현할 수 있습니다. 그러나 하나의 객체에 두 객체를 상속받지는 못합니다.
객체엔 오직 하나의 [[Prototype]]만 있습니다.
멤버변수도 중첩함수의 변수처럼 우선 자신의 영역에서 해당 변수를 찾고 없으면 상속받은 곳으로 올라가 변수를 찾습니다.

let animal = {
  eats: true,
  walk() {
    alert("동물이 걷습니다.");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// 메서드 walk는 프로토타입 체인을 통해 상속받았습니다.
longEar.walk(); // 동물이 걷습니다.
alert(longEar.jumps); // true (rabbit에서 상속받음)

프로토타입 사용

프로토타입은 프로퍼티를 읽을 때만 사용합니다. 덮어쓰거나 수정하지 않습니다.
프로토타입은 메서드를 공유하는 것이지 객체의 상태를 공유하진 않습니다.
상속 프로퍼티도 for..in문에 포함됩니다. 상속 프로퍼티를 제외하고 싶다면 obj.hasOwnProperty(key)를 사용합니다.
혹은 Object.keys(), Object,values() 같은 메서드는 대부분상속 프로퍼티를 제외하고 동작합니다.

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`객체 자신의 프로퍼티: ${prop}`); // 객체 자신의 프로퍼티: jumps
  } else {
    alert(`상속 프로퍼티: ${prop}`); // 상속 프로퍼티: eats
  }
}

생성자 함수의 prototype 프로퍼티

F.prototype 프로퍼티는 생성자 함수를 호출할 때 만들어지는 새로운 객체의 [[Prototype]]을 설정합니다.
F.prototype 프로퍼티의 값은 객체, null만 가능합니다. 다른 값은 무시됩니다.

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal
alert( rabbit.eats ); // true

constructor property
모든 함수는 기본적으로 prototype property를 갖고 있는데, 이는 constructor 프로퍼티 하나만 갖고 있는 객체입니다.

function Rabbit() {}

/* 기본 prototype
Rabbit.prototype = { constructor: Rabbit };
*/

이를 건들이지 않았다면 인스턴스 객체들이 모두 [[Prototype]]을 거쳐 constructor 프로퍼티를 사용할 수 있습니다.
그러나 자바스크립트는 알맞은 constructor 값을 보장하지 않으므로 값 프로토타입 프로퍼티 값을 통째로 바꾸기 보다는 메서드를 사용합니다.

function Rabbit() {}

// Rabbit.prototype 전체를 덮어쓰지 말고
// 원하는 프로퍼티는 그냥 추가하세요.
Rabbit.prototype.jumps = true
// 이렇게 하면 기본 Rabbit.prototype.constructor가 유지됩니다.

// 혹은 프로퍼티를 수동으로 다시 만들어줍니다.
Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// 수동으로 추가해 주었기 때문에 알맞은 constructor가 유지됩니다.

프로토타입 사용관련 메서드

Object.create(proto[, descriptors])
proto를 [[Prototype]](생성자)로 하는 객체를 새로 만듦. 얇은 복사를 이룹니다.

Object.getPrototypeOf(obj)
__proto__ getter와 같이 [[Prototype]]을 반환합니다.

Object.setPrototypeOf(obj, proto)
__proto__ setter와 같이 obj의 [[Prototype]]을 proto로 설정합니다.

위의 것들을 조합해서 새로운 객체를 만들어낼 수 있습니다.

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
Object.create(null) // 프로토타입이 없는 객체 생성

Object.keys(obj) / Object.values(obj) / Object.entries(obj) – obj 내 열거 가능한 프로퍼티 키, 값, 키-값 쌍을 담은 배열을 반환합니다.
Object.getOwnPropertySymbols(obj) – obj 내 심볼형 키를 담은 배열을 반환합니다.
Object.getOwnPropertyNames(obj) – obj 내 문자형 키를 담은 배열을 반환합니다.
Reflect.ownKeys(obj) – obj내 키 전체를 담은 배열을 반환합니다.
obj.hasOwnProperty(key) – 상속받지 않고 obj 자체에 구현된 키 중 이름이 key인 것이 있으면 true를 반환합니다.

대부분 다 해당 객체가 직접 소유한 프로퍼티만 반환합니다. 상속 프로퍼티까지 얻으려면 for..in을 사용하세요.

Class

클래스는 객체 지향 프로그래밍에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀로, 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성됩니다.

자바스크립트에서 클래스는 함수의 한 종류입니다.

class User {

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

  sayHi() {
    alert(this.name);
  }

}

// 사용법:
let user = new User("John");
user.sayHi();

정의

클래스 표현식

let User = class {
  sayHi() {
    alert("Hello");
  }
};

기명 클래스 표현식
기명 함수 표현식처럼 클래스의 이름은 클래스 내부에서만 사용할 수 있습니다.

// 기명 클래스 표현식(Named Class Expression)
// (명세서엔 없는 용어이지만, 기명 함수 표현식과 유사하게 동작합니다.)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass라는 이름은 오직 클래스 안에서만 사용할 수 있습니다.
  }
};

new User().sayHi(); // 제대로 동작합니다(MyClass의 정의를 보여줌).

alert(MyClass); // Error: MyClass is not defined, MyClass는 클래스 밖에서 사용할 수 없습니다.

클래스 동적생성

function makeClass(phrase) {
  // 클래스를 선언하고 이를 반환함
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}

// 새로운 클래스를 만듦
let User = makeClass("Hello");
new User().sayHi(); // Hello

get, set, caculated property

class User {

  constructor(name) {
    // setter를 활성화합니다.
    this.name = name;
  }
  
  ['say' + 'Hi']() {
    alert("Hello");
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("이름이 너무 짧습니다.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // 이름이 너무 짧습니다.
user.sayHi(); // Hello

summary

class MyClass {
  prop = value; // 프로퍼티

  constructor(...) { // 생성자 메서드
    // ...
  }

  method(...) {} // 메서드

  get something(...) {} // getter 메서드
  set something(...) {} // setter 메서드

  [Symbol.iterator]() {} // 계산된 이름(computed name)을 사용해 만드는 메서드 (심볼)
  // ...
}

상속

extends로 상속받음.

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  run(speed) {
    this.speed += speed;
    alert(`${this.name} 은/는 속도 ${this.speed}로 달립니다.`);
  }
  stop() {
    this.speed = 0;
    alert(`${this.name} 이/가 멈췄습니다.`);
  }
}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} 이/가 숨었습니다!`);
  }
}

let rabbit = new Rabbit("흰 토끼");

rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼 이/가 숨었습니다!

오버라이딩

메서드의 경우, 부모에게 구현된 메서드이지만, 자식이 동일한 이름으로 메서드를 구현하면 덮어씌워집니다.
생성자의 경우, super()를 통해 부모 생성자를 꼭 호출한 뒤 작성해야 합니다.

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// 이제 에러 없이 동작합니다.
let rabbit = new Rabbit("흰 토끼", 10);
alert(rabbit.name); // 흰 토끼
alert(rabbit.earLength); // 10

obj instanceof Class

객체가 특정 클래스에 속하는지 아닌지, 상속관계를 확인해줍니다.

profile
정리하는 개발자

0개의 댓글