[자바스크립트 ES6+ 심화] 1. Class

Speedwell🍀·2022년 2월 22일
0

1. 객체 지향 프로그래밍

자바스크립트는

  • 객체 지향 프로그래밍 언어

    • OOP: Object Oriented Programming
  • ECMAScript 스펙에 OOP라고 작성되어 있음

    • ECMAScript is an object-oriented programming language

객체 구성 요소

  • OOP에서 Object(객체)는

    • 자바스크립트의 Object가 아님
    • 개념적, 사상적 접근임
    • 형체, 실체가 없음
  • 행위와 속성으로 객체의 특성을 표현

  • 행위(Behavior)

    • 먹다, 마시다와 같은 동적인 모습
  • 속성(Attribute)

    • 행위의 대상이 속성

객체의 구체화

  • 객체를 코드로 구체화하면

    • 객체는 클래스(Class)
    • 행위는 메소드(method)
    • 속성은 프로퍼티(property)
  • 클래스에

    • 메소드와 프로퍼티를 작성
    • 클래스 자체로는 사용할 수 없으며
    • 인스턴스로 생성해야 사용 가능
class Point {
  constructor(point){ this.point = point; }
  getPoint(){ return this.point; }
};
const obj = new Point(100);
log(obj.getPoint());
log(obj.point);

// 100
// 100

자바스크립트로 OOP 구현

  • 다른 언어와 OOP 개념은 같지만 클래스 구조와 구현 방법이 다름
    • prototype에 메소드를 연결하는 구조
    • 연결된 메소드로 인스턴스 생성

➡ 따라서 비교하는 것은 의미가 없음

  • 자바스크립트에 적합한 방법과 자바스크립트 특징을 활용하여 OOP를 구현해야 함

2. Class 선언, Class 구조

Class 선언문

  • Syntax

    • class Name { body }
  • 클래스 작성 방법

    • class 키워드에 이어 클래스 이름 작성
    • 이름의 첫문자는 대문자를 사용 (개발자들 사이의 관례)
    • 블록{}을 작성하고 블록 안에 메소드를 작성
class Point {
  getPoint(){
    return 100;
  }
};
const obj = new Point();
log(obj.getPoint());

// 100
  • 대문자 Class는 개념적인 클래스를 뜻하고, 소문자 class는 키워드

Class 표현식

  • Syntax

    • const/let Name = class { body }
  • 클래스 작성 방법

    • 변수 이름 Name이 클래스 이름이 됨
    • 변수에 Class 오브젝트를 할당하는 형태
    • 다른 것은 클래스 선언문과 같음
const Point = class {
  getPoint(){
    return 100;
  }
};
const obj = new Point();
log(obj.getPoint());

// 100
  • 클래스 형태
const Point = class {
  getPoint(){
    return 100;
  }
};
/*
1. Point를 펼치면 프로퍼티, prototype, __proto__가 있음

2. prototype을 펼치면 constructor가 있으며, getPoint()가 있음

3. constructor는 Point 클래스 전체를 참조

4. 클래스에 메소드를 작성하면 prototype에 연결됨
- Point.prototype.getPoint = function(){} 형태로 작성한 것과 같음

5. __proto__에서 빌트인 Function 오브젝트의 prototype에 연결된 메소드를 참조
*/

const obj = new Point();
/*
1. Point 클래스로 인스턴스를 생성

2. obj를 펼치면 __proto__가 있으며 constructor와 getPoint()가 있음

3. Point.prototype에 연결된 메소드로 인스턴스를 생성하고 __proto__에서 참조할 수 있도록 만듦
*/

console.log(obj.getPoint());
/*
1. obj 인스턴스의 getPoint() 메소드를 호출
- obj.__proto__에 연결된 getPoint()가 호출됨
*/

const, let 사용 기준

  • 강좌의 const, let 사용 기준

    • 값이 대체되지 않으면 const를 사용
    • 값이 대체되면 let을 사용
  • 오브젝트의 프로퍼티가 변경되더라도

    • 오브젝트 자체가 대체되지 않으면 const
    • Class, Array, 인스턴스

함수, 메소드 기준

  • 강좌의 함수, 메소드 사용 기준

  • 함수

    • 인스턴스를 생성하지 않고 직접 호출
log(Array.isArray([]));

const point = {
  getPoint(){
    return 100;
  }
};
log(point.getPoint());

// true
// 100
  • 메소드
    • 인스턴스를 사용하여 호출하는 함수로 prototype에 연결됨

1) 클래스에 작성한 함수

class Point {
  getPoint(){
    return 100;
  }
};
const obj = new Point();
log(obj.getPoint());

// 100

2) prototype에 연결된 function

const Point = function(){};
Point.prototype.getPoint = function(){
  return 100;
};
const obj = new Point();
log(obj.getPoint());

// 100

3) 빌트인 오브젝트의 prototype에 연결된 함수

const list = [];
list.push("책");
log(list);

// [책]

3. Class 작성 기준, computed name

Class 작성 기준

  • 클래스는 strict 모드에서 실행되므로 이에 맞추어 코드를 작성해야 함

  • 클래스에 메소드 작성 방법

    • function 키워드를 작성하지 않음
    • 메소드와 메소드 사이에 콤마(,)를 작성하지 않음
    • 세미콜론(;) 작성은 선택
class Point {
  setPoint(point){
    this.point = point;
  }
  getPoint(){
    return this.point;
  };
};
log(typeof Point);

// function
  • 클래스의 typeof는 function
    • Class 타입이 별도로 있지 않음

computed name

  • 메소드 이름을 조합하여 사용
    • 대괄호[] 안에 조합할 이름을 작성
    • 조합한 결과가 메소드 이름이 됨
const name = "Point";
class Point {
  static ["get" + name](add){
    return add ? 100 : 50;
  }
};
log(Point["get" + name](true));

// 100

Class 작성 기준

  • 메소드를 prototype에 연결하여 작성하지 않음
const Point = class {
  setPoint(point){
    this.point = point;
  }
};
log(Point.prototype.setPoint);

// setPoint(point){ this.point = point; }
  • 클래스 밖에서 메소드를 prototype에 연결할 수 있음
const Point = class { };
const obj = new Point();
Point.prototype.getPoint = function(){
  return 100;
};
log(obj.getPoint());

// 100
  • 클래스는 열거할 수 없음

  • prototype에 메소드 추가

const Book = class {
  setTitle(title){
    this.title = title;
  }
};
/*
1. Book을 펼치면, 프로퍼티와 prototype이 있음
2. prototype을 펼치면, setTitle()이 있음
*/

const obj = new Book();
obj.setTitle("자바스크립트");
/*
1. obj를 펼치면, title 프로퍼티가 있으며 이것은 setTitle()에서 설정한 것
2. title처럼 인스턴스에 바로 연결된 프로퍼티를 인스턴스 프로퍼티라고 부름
3. obj.__proto__를 펼치면, setTitle()이 있음
4. 인스턴스 프로퍼티는 __proto__ 위에 표시되며 메소드는 __proto__ 안에 표시됨
*/

Book.prototype.getTitle = function(){
  return this.title;
};
/*
1. Book.prototype에 getTitle()이 추가됨
2. obj.__proto__에 getTitle()이 표시됨
3. prototype에 메소드를 추가로 연결하면 생성된 모든 인스턴스에서 메소드를 사용할 수 있음
4. 이것을 prototype sharing(공유)라고 부름
*/

console.log(obj.getTitle());
/*
1. obj 인스턴스의 getTitle() 메소드가 호출되며 "자바스크립트"를 반환함
*/

4. constructor, constructor 반환

constructor

  • constructor는 생성자로 인스턴스를 생성하고 초기화함

  • ES5까지는 constructor를 작성할 수 없었으나 ES6부터는 작성할 수 있음

class Point {
  constructor(point){
    this.point = point;
  }
};
const obj = new Point(100);

/*
*** 인스턴스 생성 방법
1. const obj = new Point(100);
- new 연산자가 Point 클래스 오브젝트의 constructor를 호출

2. constructor(point){...}
- point 파라미터 값은 100이 됨

3. 엔진은 빈 오브젝트{}를 생성 => 이것이 인스턴스

4. 인스턴스에 프로퍼티 이름과 값을 설정하여 인스턴스 구조를 만듦. __proto__, __proto__.constructor 등

5. constructor 블록의 코드를 실행

6. this.point = point;
- this가 생성한 인스턴스를 참조. 인스턴스{}를 먼저 생성하므로 this로 참조할 수 있음

7. ponit는 인스턴스 프로퍼티가 됨. point 파라미터 값이 100이므로 point 프로퍼티 값은 100이 됨

8. 생성한 인스턴스를 반환
*/

constructor 미작성

  • constructor를 작성하지 않은 상태에서
    • new 연산자로 인스턴스를 생성하면
    • prototype에 연결된 constructor가 호출됨
class Point {
  setPoint(point){
    this.point = point;
  }
};
const obj = new Point();
obj.setPoint(100);

/*
1. 엔진이 class 키워드를 만나 Point 클래스 오브젝트를 생성할 때 constructor에서 클래스 전체를 참조하도록 환경을 만듦

2. constructor를 작성하지 않으면 prototype.constructor를 사용하므로 인스턴스를 생성할 수 있지만 인스턴스에 초기값을 설정할 수 없음

3. 클래스에 constructor를 작성하면 prototype.constructor를 오버라이드하게 됨
*/

constructor 반환

  • constructor에 return을 작성하지 않으면

    • 생성한 인스턴스를 반환
  • constructor에서 Number, String을 반환하면

    • 이를 무시하고 인스턴스를 반환
class Point {
  constructor(point){
    this.point = point;
    return point;
  }
};
const obj = new Point(100);
log(obj.point);
log(obj instanceof Point);

// 100
// true
  • constructor에서 Object를 반환하면
    • 인스턴스를 반환하지 않고 Object 반환
class Point {
  constructor(point){
    return {point: point};
  }
};
const obj = new Point(100);
log(obj);
log(obj instanceof Point);

// {point: 100}
// false

5. getter, setter, static 메소드, 호이스팅

getter

  • getter는 메소드를 호출하여 값을 구함

    • 메소드를 호출할 때는 name()처럼 소괄호()를 작성하지만
    • getter는 소괄호()를 작성하지 않고 name만 작성
    • 파라미터 사용 불가
  • 클래스에 getter 작성 방법

class Point {
  constructor(point){
    this.point = point;
  }
  get getPoint(){
    return this.point;
  }
};
const obj = new Point(100);
log(obj.getPoint);

// 100

setter

  • setter는 메소드를 호출하여 값을 설정
    • setter도 getter처럼 소괄호()를 작성하지 않고 이름만 작성
  • 클래스에 setter 작성 방법
class Point {
  set setPoint(point){
    this.point = point;
  }
};
const obj = new Point();
obj.setPoint = 100;
log(obj.point);

// 100

static 메소드

  • Syntax

    • static name(){...}
  • static 메소드 작성 방법

class Point {
  static getPoint(){
    return 100;
  }
};
log(Point.getPoint());

// 100
  • static 메소드의 구조적 특징
    • prototype이 아닌 클래스에 연결되며
    • 생성한 인스턴스에 할당되지 않음
class Point {
  static getPoint(){
    return 100;
  }
};
const obj = new Point();
log(obj.getPoint);

// undefined

호이스팅

  • 클래스는 호이스팅(hoisting)되지 않음
    • const, let 변수처럼
    • class 키워드가 작성된 위치에서 클래스 이름 선언과 오브젝트 생성을 동시에 하기 때문
try {
  const obj = Point;
} catch {
  log("호이스팅 불가");
};

class Point {
  static getPoint() {
    return 100;
  }
};
log(Point.getPoint());

// 호이스팅 불가
// 100

new.target

  • new.target 프로퍼티는

    • 함수 또는 생성자가 new 연산자로 호출된 여부를 반환
  • new 연산자로 constructor를 호출하면

    • new.target은 constructor를 참조
class Point {
  constructor(){
    log(new.target.name);
  }
};
new Point();

// Point
  • 함수로 호출하면 undefined 반환
function book() {
  log(new.target);
};
book();

// undefined

6. 상속

  • 상속(Inheritance)은 OOP 기능 중 하나

    • 클래스에 다른 클래스를 포함시키는 형태
    • 따라서 포함시킨 클래스의 메소드와 프로퍼티를 내 것처럼 사용 가능
  • 상속해주는 클래스, 상속받을 클래스를

    • 부모 클래스, 슈퍼(super) 클래스라고 부름
    • 강좌에서는 슈퍼 클래스로 표기
    • super 키워드로 슈퍼 클래스 참조
  • 상속받는 클래스를

    • 자식 클래스, 서브(sub) 클래스라고 부름
    • 강좌에서는 서브 클래스로 표기

extends 키워드

  • Syntax

    • subClass extends superClass {...}
  • extends 키워드로 상속을 구현

class Book {
  constructor(title){
    this.title = title;
  };
  getTitle(){
    return this.title;
  }
};
class Point extends Book {
  setPoint(point){
    this.point = point;
  }
};
const obj = new Point("책");
log(obj.getTitle());

// 책
  • 상속 구조
class Book {
  constructor(title){
    this.title = title;
  };
  getTitle(){
    return this.title;
  }
};
/*
1, 엔진이 Book.prototype.getTitle() 형태로 만듦
*/

class Point extends Book {
  setPiont(point){
    this.point = point;
  }
};
/*
1. Book {setPoint(point){...}}
- setPoint()는 Point 클래스의 메소드이며
- Point.prototype에 연결됨

2. 엔진이 extends 키워드를 만나면
- Point 클래스에서 Book 클래스를 상속받아 
- 서브와 슈퍼 구조를 만듦

3. Point.prototype.__proto__를 펼치면 
- getTitle()이 있으며 
- Book.prototype에 연결된 메소드

4. prototype.__proto__에 상속해주는 클래스의 
- prototype에 연결된 메소드를
- 구조적, 계층적으로 만듦

=> 이것이 상속
*/

/*
Point.__proto__를 펼치면 상속받은 Book 클래스 전체가 표시됨
*/

const obj = new Point("책");
/*
1. obj를 펼치면 {title: "책"}이 있으며
- 이것은 인스턴스 프로퍼티

2. 이런 방법으로 인스턴스마다 
- 고유의 프로퍼티 값을 가질 수 있음

3. 고유의 값을 가지는 것이 인스턴스의 가장 큰 목적

4. 상속이 클래스의 가장 큰 목적이 아님
- 상속은 인스턴스 프로퍼티를 지원하기 위한 수단

5. obj.__proto__를 펼치면 setPoint()가 있으며
- 이것은 서브 클래스의 메소드

6. obj.__proto__.__proto__를 펼치면 getTitle()이 있으며
- 이것은 슈퍼 클래스의 메소드

7. 이처럼 __proto__를 사용하여
- 슈퍼 클래스의 prototype에 연결된 메소드를
- 구조적, 계층적으로 연결
=> 이것이 상속 구조

8. 인스턴스의 메소드를 호출하면
- __proto__ 구조를 따라 아래로 내려 가면서 메소드를 식별
- 식별하는 위치에 메소드가 있으면 실행
*/
  • 메소드 오버라이딩(Overriding)
class Point extends Book {
  getTitle(){
    return "서브 클래스";
  }
};
/*
1. 오버라이드 설명을 위해 Point 클래스에도 getTitle() 작성함

2. getTitle()이 양쪽 클래스에 있음
*/

const obj = new Point("책");
/*
1. obj.__proto__를 펼치면 getTitle()이 있으며
= 이것은 서브 클래스의 메소드

2. obj.__proto__.__proto__를 펼치면 getTitle()이 있으며
- 이것은 슈퍼 클래스의 메소드
*/

console.log(obj.getTitle());
/*
1. obj.getTitle()을 호출하면
- 먼저 서브 클래스에서 찾기
- 즉, obj.__proto__에서 찾기

2. 없으면 슈퍼 클래스에서 찾기
- 즉, obj.__proto__.__proto__에서 찾기

3. obj.__proto__에 즉, 서브 클래스에
- getTitle()이 있으므로 이것을 호출

4. 이것이 메소드 오버라이딩
*/

7. super 키워드, constructor 호출

super 키워드

  • 슈퍼 클래스와 서브 클래스에
    • 같은 이름의 메소드가 있으면 서브 클래스의 메소드가 호출됨
  • super 키워드를 사용하여
    • 슈퍼 클래스의 메소드를 호출할 수 있음
    • super.getTitle() 형태
class Book {
  getTitle(){
    log("슈퍼")
  }
};
class Point extends Book {
  getTitle(){
    super.getTitle();
    log("서브");
  }
};
new Point().getTitle();

// 슈퍼
// 서브

constructor 호출

  • 서브와 슈퍼에 constructor를 모두 작성하지 않으면
    • 디폴트 constructor가 호출됨
class Book {
  setTitle(title){
    this.title = title;
  }
};
class Point extends Book {
};
const obj = new Point();
obj.setTitle("책");
log(obj.title);

// 책
  • 서브에 작성하지 않고 슈퍼에만 작성하면
    • 파라미터 값을 슈퍼로 넘겨줌
class Book {
  constructor(title){
    this.title = title;
  };
};
class Point extends Book {
};
const obj = new Point("책");
log(obj.title);

// 책
  • 서브에는 작성하고 슈퍼에 작성하지 않으면
    • 에러 발생
class Book {
  setTitle(title){
    this.title = title;
  }
};
class Point extends Book {
  // constructor(point){
  // 	this.point = point;
  // };
};
const obj = new Point(100);
  • 서브와 슈퍼에 constructor를 모두 작성하면
    • 서브에서 super()로 호출해야 함
class Book {
  constructor(title){
    this.title = title;
  };
};
class Point extends Book {
  constructor(title, point){
    super(title);
    this.point = point;
  };
};
const obj = new Point("책", 100);
log(`${obj.title}, ${obj.point}`);

// 책 100  

8. Built-in, DOM 오브젝트 상속

Built-in 오브젝트 상속

  • 빌트인 오브젝트를 상속받을 수 있음
    • 인스턴스가 빌트인 오브젝트의 특징을 갖게 되며
    • this로 빌트인 오브젝트에 접근할 수 있음
    • extends 기워드로 구현
class Point extneds Array {
  constructor(){
    super();
  }
  getTotal(){
    let total = 0;
    for (const value of this){
      total += value;
    };
    return total;
  }
};
const obj = new Point();
obj.push(10, 20, 30);
log(obj.getTotal());

// 60
  • 코드 프로세스
    • class Point extends Array {...}
    • const obj = new Point();
    • constructor(){ super() }
    • obj.push(10, 20, 30);
    • obj.getTotal()
    • for (const value of this){...}

Object 상속

  • Object는 클래스가 아니므로

    • 다른 Object를 상속받을 수 없지만
    • 상속받으면 __proto__ 구조가 되는 것을 활용하여 상속을 수현할 수 있음
  • Object 상속

    • Object.setPrototypeOf()으로
    • __proto__ 구조를 만듦
const Book = {
  getTitle(){
    log("Book");
  }
};
const Point = {
  getTitle(){
    super.getTitle();
  }
};
Object.setPrototypeOf(Pont, Book);
Point.getTitle();

// Book

Image 오브젝트 상속

  • Image 오브젝트 상속 코드
class Home extends Image {
  constructor() {
    super();
  };
  setAttr(){
    this.src = "../../image/rainbow.png";
    this.alt = "집과 나무가 있고 " + "무지개가 있는 모습";
    this.title = "무지개";
  }
};
const obj = new Home();
obj.setAttr();
document.querySelector("#show").appendChiled(obj);
  • super();

    • Image 오브젝트의 constructor를 호출
  • this.src, this.alt, this.title

    • Image 오브젝트를 this로 참조
    • Image 속성에 값을 할당

Audio 오브젝트 상속

  • Audio 오브젝트 상속
class Music extends Audio {
  constructor() {
    super();
  };
  setAttr(src, controls, muted, loop){
    this.src = src;
    this.controls = controls;
    this.muted = muted;
    this.loop = loop;
  }
};
const obj = new Music();
const src = "../../image/Beet5.ogg";
obj.setAttr(src, true, true, true);
document.querySelector("#show").appendChiled(obj);
  • super();

    • Audio 오브젝트의 constructor를 호출
  • this.src, this.controls

    • Audio 오브젝트를 this로 참조
    • Audio 속성에 값을 할당
    • 파라미터 값을 받아 속성값을 설정하면 범용 클래스로 사용할 수 있음

9. this 참조, Generator

this 참조

  • 인스턴스.메소드() 형태로 호출하면

    • 메소드에서 this가 인스턴스를 참조
  • static 메소드에서 this는

    • 메소드가 속한 클래스를 참조
class Point {
  static setPoint(point){
    this.value = point;
  };
};
Point.setPoint(100);
log(Point.value);
log(new Point().value);

// 100
// undefined
  • static property
class Point {
  static value = 100;
};
log(Point.value);
Point.bonus = 300;
log(Point.bonus);
log(new Point().value);

// 100
// 300
// undefined
  • constructor에서
    • this.constructor는 생성하는 인스턴스가 아니라
      -클래스 오브젝트를 참조
class Point {
  constructor(){
    log(this.constructor.get());
  }
  static get(){
    return 100;
  }
};
new Point();

// 100

Generator

  • 클래스의 제너레이터 함수는
    • prototype에 연결됨
    • 인스턴스로 호출해야됨
class Point {
  *getPoint() {
    yield 10;
    yield 20;
  }
};
const gen = new Point();
const obj = gen.getPoint();
log(obj.next());
log(obj.next());
log(obj.next());

// {value: 10, done: false}
// {value: 20, done: false}
// {value: undefined, done: true}

0개의 댓글