자바스크립트 OOP [1] – 생성자 함수, new 연산자, 프로토타입, ES6 클래스, Setter/Getter, 스태틱 메소드, Object.create

자바스크립트의 OOP





  • 자바스크립트의 객체는 프로토타입 객체(Prototype object)와 연결(linked)되어 있다
    • 이러한 연결을 ‘Prototypal inheritance’라는 용어로 일컫는다.
    • 일반적인 OOP에서 객체(instance)는 클래스로부터 인스턴스화(instantiated) 된다고 표현한다.
  • 프로토타입은 메소드(behavior)를 가지고 있으며, 프로토타입에 연결된 모든 Object는 이 메소드에 접근할 수 있다.
    • 일반적인 OOP의 클래스의 상속 개념이랑은 다르니 참고하자.
  • 자바스크립트 OOP에서 behavior는 프로토타입 오브젝트에 위임(delegated) 된다.
    • 일반적인 OOP에서는 복사(copied) 된다.
  • 자바스크립트에서 OOP를 하기 위해 프로토타입을 만드는 방법은 크게 세가지로 나눠볼 수 있다.

생성자 함수와 new 연산자(Constructor Functions and new Operator)

const Person = function (firstName, birthYear) {
  // Instance properties
  this.firstName = firstName;
  this.birthYear = birthYear;

  // // Never do this - copied to every instance
  // this.calcAge = function () {
  //   console.log(2037 - this.birthYear);
  // }
};

const jonas = new Person('Jonas', 1991);
console.log(jonas);

// 1. New {} is created
// 2. function is called, this = {}
// 3. {} linked to prototype
// 4. function automatically return {}

console.log(jonas instanceof Person); // true
  • 생성자 함수와 new 연산자를 사용한 객체(인스턴스)의 생성은 네 단계로 이루어진다.
    • 1. New empty object({}) is created
    • 2. Constructor function is called, this keyword is set to the new object.
    • 3. New object is linked to prototype(__proto__ property)
    • 4. Function automatically return new object.

프로토타입

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
}

jonas.calcAge();
matilda.calcAge();
  • 생성자 함수 내부에 메소드를 작성하면 모든 인스턴스에 해당 내용이 복사되므로 비효율적이다. prototype 프로퍼티를 통해 외부에서 메소드를 작성해주면 모든 인스턴스에서 접근이 가능하다.
console.log(jonas.__proto__);
console.log(jonas.__proto__ === Person.prototype); // true

console.log(Person.prototype.isPrototypeOf(jonas)); // true
  • __proto__ 프로퍼티를 사용하면 원형 프로토타입을 가리킬 수 있다.
  • .isPrototypeOf(인스턴스) 메소드는 해당 프로토타입의 인스턴스인지를 boolean 타입으로 리턴한다.
Person.prototype.species = 'Homo Sapienns'
console.log(jonas.hasOwnProperty('firstName')); // true
console.log(jonas.hasOwnProperty('species')); // false
  • .hasOwnProperty 메소드는 프로토타입 안에 명명된 Instance property만 true를 리턴한다.

Prototypal Inheritance

  • 객체는 생성자 함수에서 작성한 프로퍼티나 메소드를 갖고 있지만, 호출된 프로퍼티나 메소드가 없으면 Prototype에서 찾아보게 된다. 따라서 Prototype에 설정된 calcAge 메소드가 호출될 수 있는 것이다.

Prototype Chain

  • Prototype 역시 Prototype을 가지고 있는데, 그것이 Object.prototype이다.
    • jonas.__proto__.__proto__; // Object.protytpe
  • 이러한 객체 간 연결성을 Prototype Chain이라고 부른다. JS 엔진은 스코프 체인처럼 호출한 프로퍼티나 메소드가 없으면 상위 프로토타입에서 찾아보게 된다.

JS 빌트인 객체(e.g. array)의 prototypal inheritance

const arr = [3, 6, 4, 5, 6, 9, 3];
console.log(arr.__proto__); 

Array.prototype.unique = function () {
  return [...new Set(this)]
}

console.log(arr.unique());
  • array의 수많은 메소드를 사용할 수 있는 것은 Array.prototype이 가진 메소드를 호출할 수 있기 때문이다.
  • 이러한 방식으로 모든 array가 사용할 메소드를 추가할 수 있다. 하지만 빌트인 오브젝트를 수정하는 것은 위험한 일이므로 사용하지 말자.

ES6 클래스

// class expressob
// const PersonCl = class {
// }

// class declaration
class PersonCl {
  constructor(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  }
  // 프로토타입 프로퍼티로 추가되는 메소드
  calcAge() {
    console.log(2037 - this.birthYear);
  }
}

const jessica = new PersonCl('Jessica', 1996);
console.log(jessica);
jessica.calcAge();
console.log(jessica.__proto__ === PersonCl.prototype);
  • JS의 클래스는 내부 작동 방식은 여전히 Prototype과 똑같은 방식이지만 신택스를 다른 언어처럼 좀 더 친숙하고 간결하게 사용할 수 있도록 등장한 개념이다.
  • 메소드를 추가하고 싶으면 프로토타입의 메소드로 추가하지 않고, class 내부에서 선언하면 되므로 코드가 조금 더 간결해진다.
  • 클래스는 호이스팅 되지 않으므로 선언 이전에 사용하지 못한다.
  • 클래스는 일급 시민 객체(first-class citizens)이다.
  • 클래스는 strict 모드에서 실행된다.

Setter / Getter

const account = {
  owner: 'jonas',
  movements: [200, 530, 120, 300],

  // getter
  get latest() {
    return this.movements.slice(-1).pop();
  },

  // setter
  set latest(mov) {
    this.movements.push(mov);
  }
}

// 사용할 때 프로퍼티처럼 사용할 수 있다.
console.log(account.latest);
account.latest = 50;
  • setter, getter를 사용하면 메소드의 형식으로 코드를 작성하지만, 호출할 때는 프로퍼티처럼 사용할 수 있다.
class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }
  // 프로토타입 프로퍼티로 추가되는 메소드
  calcAge() {
    console.log(2037 - this.birthYear);
  }

  // Set a property that already exist
  set fullName(name) {
    if (name.includes(' ')) this._fullName = name;
    else alert((`${name} is not full name`));
  }

  get fullName() {
    return this._fullName;
  }
}
  • 주로 Data validation을 해야할 때 사용한다. 이미 생성자 함수 내에 존재하는 프로퍼티명을 사용할 때의 패턴을 기억하자.

Static Methods

Array.from(document.querySelectorAll('h1'));
Number.parseFloat(12);
  • 정적 메소드는 객체(인스턴스)가 아닌 클래스(프로토타입)에서 직접 호출할 수 있는 메소드를 의미한다.
Person.hey = function () {
  console.log('Hey!');
}

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  calcAge() {
    console.log(2037 - this.birthYear);
  }

  static hey() {
    console.log('Hey');
  }
}
  • 외부에서 직접 선언해주거나 static 키워드를 사용해서 내부에서 선언할 수 있다.

Object.create

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },

  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  }
};

const steven = Object.create(PersonProto);
console.log(steven); // {} which has __proto__
steven.name = 'Steven';
steven.birthYear = 2002;
steven.calcAge();

console.log(steven.__proto__);

const sarah = Object.create(PersonProto);
sarah.init('Sarah', 1979);
sarah.calcAge();
  • Object.create는 생성자 함수나 클래스와는 다른 방식으로 프로토타입과 객체와 연결하는 방식이다.
  • Object.create를 사용하면 자동으로 .__proto__를 통해 PersonProto라는 프로토타입과 연결된다.

Leave a Reply

Your email address will not be published. Required fields are marked *