javascript & Node.js/javascript

자바스크립트 프로토타입과 클래스

반응형
  • 자바스크립트는 프로토타입 기반 언어
  • 클래스 기반 언어에서는 상속을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형으로 삼고 이를 복제함으로써 상속과 비슷한 효과를 얻는다.


프로토타입의 개념 이해

constructor, prototype, instance

프로토타입 도식

  • 어떤 생성자 함수를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
  • 이때 instance에는 proto 라는 프로퍼티가 자동으로 부여되는데, 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.

prototype이라는 프로퍼티와 proto라는 프로퍼티가 새로 등장했는데, 이 둘의 관계가 프로토타입 개념의 핵심이다.

 

Prototype

const person = new Person('will');
Person.prototype ==== person.__proto__ // true

// 인스턴스의 __proto__가 생성자의 prototype 프로퍼티를 참조한다.
  • new 연산자로 Constructor를 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 proto는 Constructor의 prototype을 참조함.
  • 생성자 함수의 prototype에 어떤 메소드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 메소드나 프로퍼티에 접근할 수 있게 된다.
const Person = function (name) {
  this._name = name;
};

Person.prototype.getName = function () {
  return this._name;
};
// Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다.

const man = new Person("will");
console.log(man.getName()); // will
// __proto__는 생략 가능한 프로퍼티로 man.__proto__.getName()이 아니라 생략 가능.
  • prototype 객체 내부에는 인스턴스가 사용할 메소드를 저장한다.
  • 그러면 인스턴스에서도 숨겨진 프로퍼티인 **proto를 통해 이 메소드들에 접근할 수 있게 된다.**

 

생성자 프로퍼티

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있다. (인스턴스의 proto 객체 내부에도 마찬가지)

이 constructor 프로퍼티는 원래의 생성자 함수(자기 자신)을 참조 한다.

이 프로퍼티를 통해 그 원형이 무엇인지 알 수 있다.

const arr = [1, 2];
Array.prototype.constructor === Array // true
arr.__proto__.constructor === Array // true

arr.constructor === Array // true
// 인스턴스의 __proto__가 생성자 함수의 prototype 프로퍼티를 참조하며 __proto_가 생략 가능해서
// 인스턴스에서 직접 constructor에 접근할 수 있음.
  • constructor는 읽기 전용 속성이 부여된 예외적인 경우(기본형 리터럴 변수 - number, string, boolean)를 제외하고는 값을 바꿀 수 있다.
  • 이러한 이유로 어떤 인스턴스의 생성자 정보를 알아내기 위해 constructor 프로퍼티에 의존하는 게 항상 안전하지는 않음.

 

오버라이딩

const Person = function (name) {
  this.name = name;
};

Person.prototype.getName = function () {
  return this.name;
};

const man = new Person("will");
man.getName = function () { // 메소드 오버라이딩 
  return "바보 " + this.name;
};

console.log(man.getName()); // 바보 will
  • 위처럼 인스턴스가 동일한 이름의 프로퍼티 또는 메소드를 가지고 있을때 proto.getName이 아닌 man 객체에 있는 getName 메소드가 호출되었음을 볼 수 있다.
  • 이러한 현상을 메소드 오버라이딩이라고 한다.
  • 즉 원본이 그대로 있는 상태에서 다른 대상을 그 위에 얹는 것.

 

오버라이딩 동작 원리

자바스크립트 엔진이 getName이라는 메소드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색하고, 없으면 그 다음으로 가까운 대상인 proto를 검색하는 순서로 진행된다.

그러므로 proto에 있는 메소드는 자신에게 있는 메소드보다 검색 순서에서 밀려 호출되지 않는 것임.

 

Object 프로토타입

  • 어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에, Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 된다.

자바스크립트는 프로토타입 기반 언어라서 상속 개념이 존재하지 않는다. 이는 클래스 기반의 다른 언어에 익숙한 많은 개발자들을 혼란스럽게 했고, ES6에는 클래스 문법이 추가됐다. 다만 ES6에서도 일정 부분은 프로토타입을 활용하고 있음.

자바스크립트의 클래스

  • 생성자 함수 Array를 new 연산자와 함께 호출하면 인스턴스가 생성된다.
  • 이때 Array를 일종의 클래스라고 하면, Array의 prototype 객체 내부 요소들이 인스턴스에 상속된다고 볼 수 있다.
  • 엄밀히는 상속이 아닌 프로토타입 체이닝에 의한 참조지만 결과적으로는 동일하게 동작한다.
  • 한편 Array 내부 프로퍼티 중 prototype 프로퍼티를 제외한 나머지는 인스턴스에 상속되지 않는다.
  • 인스턴스에 상속되는지 여부에 따라 스태틱 멤버와 프로토타입 메소드(인스턴스 멤버)로 나뉜다.
const Rectangle = function (width, height) {
  this.width = width;
  this.height = height;
};

// 프로토타입 메소드
Rectangle.prototype.getArea = function () {
  return this.width * this.height;
};

// 스태틱 메소드
Rectangle.isRectangle = function (instance) {
  return (
    instance instanceof Rectangle && instance.width > 0 && instance.height > 0
  );
};

const rect = new Rectangle(3, 4);
console.log(rect.getArea()); // rect.__proto__.getArea에 접근
console.log(rect.isRectangle(rect)); // Error
console.log(Rectangle.isRectangle(rect));
  • getArea는 실제로는 rect.proto.getArea에 접근하는데, 이때 proto를 생략했으므로 this가 rect인 채로 실행될테니, 결과로는 rect.width * rect.height의 계산값이 반환된다.
  • 이처럼 인스턴스에서 직접 호출할 수 있는 메소드가 바로 프로토타입 메소드이다.
  • 한편 isRectangle이라는 메소드처럼 인스턴스에서 직접 접근할 수 없는 메소드를 스태틱 메소드라고 한다.
  • 스태틱 메소드는 생성자 함수를 this로 해야만 호출할 수 있다.

 

클래스 상속

ES5 vs ES6 클래스 문법 비교

// ES5
const ES5 = function (name) {
  this.name = name;
};

ES5.staticMethod = function () {
  return this.name + " staticMethod";
};

ES5.prototype.method = function () {
  return this.name + " method";
};

const es5 = new ES5("es5");
console.log(ES5.staticMethod());
console.log(es5.method());

// ES6
class ES6 {
  constructor(name) {
    this.name = name;
  }
  static staticMethod() {
    return this.name + " staticMethod";
  }
  method() {
    return this.name + " method";
  }
}

const es6 = new ES6("es6");
console.log(ES6.staticMethod());
console.log(es6.method());

 

ES5까지의 상속 구현

  • ES5까지에는 프로토타입 체인을 활용한 클래스 상속 구현했었음.

 

ES6부터의 상속 구현

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  constructor(width) {
    super(width, width);
  }
  getArea() {
    console.log("size is ", super.getArea());
  }
}

const square = new Square(5);
square.getArea();
반응형

'javascript & Node.js > javascript' 카테고리의 다른 글

자바스크립트 클로저  (0) 2021.01.28
자바스크립트 콜백 함수  (0) 2021.01.26
자바스크립트 this  (0) 2021.01.26
자바스크립트 실행 컨텍스트  (0) 2021.01.25