본문 바로가기
FE/JavaScript

[JavaScript] 프로토타입

by thedev 2023. 3. 17.

 

프로토타입

 


 

# 프로토타입이란

 

 객체지향 언어는 프로토타입 기반과 클래스 기반으로 나뉘어지는데, 자바스크립트는 프로토타입 기반 객체 지향 언어이다. 원시 값을 제외한 나머지들이 모두 객체이다. 상속은 객체지향 프로그래밍의 핵심 개념으로, 프로토타입 기반 언어에서는 프로토타입으로 삼은 객체를 참조함으로써 상속을 구현한다. 즉, 자바스크립트의 모든 객체는 프로토타입이라는 객체를 가지고 모든 객체는 그들의 프로토타입으로부터 프로퍼티와 메소드를 상속받는다. 자바스크립트의 모든 객체는 최소한 하나 이상의 다른 객체로부터 상속을 받으며, 이때 상속되는 정보를 제공하는 객체가 프로토타입인 것이다.

 

 

# 예시 : 배열.map을 사용할 수 있는 이유

 

예를 들어보자. 다음과 같은 객체가 있다.

 

  const user = {
    name: "kim",
    age: 20,
    id: 123,
  };
  
  console.log(user.name); // "kim"
  console.log(user.age); // 20
  console.log(user.id); // 123

 

 user라는 객체 내부에 name, age, id라는 값이 있으므로 user.name, user.age, user.id라는 표현이 가능하다. 이렇게 객체 내부에 있는 값은 .(이름)으로 접근할 수 있다.

 

  const arr = [1, 2, 3, 4, 5];
  const arr2 = arr.map(x => x * 2);

 

 그럼 이 경우는 어떻게 된 걸까? 배열.map는 정말 자주 사용하는 표현이지만, 가만히 생각해보면 arr에 map라는 메서드를 준 적이 없다. 그러나 arr에 map을 사용할 수 있다. 바로 이것이 프로토타입에 의해서 가능해진다.

 

 map의 풀네임(?)은 Array.prototype.map()이다. 즉 우리가 배열을 선언할 때, 배열은 Array라는 프로토타입을 가지게 되고, 이 Array라는 프로토타입, 즉 Array라는 객체 내부에 map이라는 메서드가 이미 포함되어 있는 것이다. 따라서 우리가 배열을 선언하면 Array의 프로토타입 속 map에 접근하여 해당 메서드를 사용할 수 있게 된다.

 

 이렇게 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체인 프로토타입과 연결되어 있다. 이것은 마치 객체지향의 상속과 비슷하게 부모 객체의 속성과 메서드를 상속받아 사용할 수 있게 한다.

 

 

# 프로토타입과 상속

 

 자바스크립트는 프로토타입을 기반으로 상속을 구현하고, 중복을 제거한다.

 

function Add(num) {
  this.num = num;
  this.addNumber = function () {
    return this.num + 1;
  };
}

const result1 = new Add(1);
const result2 = new Add(2);

console.log(result1.addNumber()); // 2
console.log(result2.addNumber()); // 3

 

 생성자 함수로 객체를 생성해보자. 입력값인 num은 계속해서 바뀌지만, addNumber라는 내부 메서드는 내용이 동일하다. num 값만 달라지고 메서드의 구성은 똑같은데, 객체를 생성할 때마다 메서드도 계속 중복으로 생성된다. 이는 매우 비효율적이다. 중복되는 메서드는 단 한 번만 생성하여 공유하는 것이 효율적이다.

 

function Add(num) {
  this.num = num;
}

Add.prototype.addNumber = function () {
  return this.num + 1;
};

const result1 = new Add(1);
const result2 = new Add(2);

console.log(result1.addNumber()); // 2
console.log(result2.addNumber()); // 3

 

 상위(부모) 역할을 하는 prototype을 지정하여 메서드를 공유할 수 있다. 이렇게 중복되는 메서드는 따로 빼서 프로토타입 기반 상속으로 공유하면 메모리 낭비를 줄일 수 있다.

 

 

# 프로토타입 객체

 

 프로토타입을 통해 하위 객체는 상위 객체에게 프로퍼티를 상속받을 수 있다. 즉, 프로토타입을 상속받은 하위 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 사용할 수 있다. 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가진다. 객체가 생성될 때 프로토타입이 결정되고, [[Prototype]]에 프로토타입의 참조가 저장된다. 내부 슬롯에 직접 접근할 수는 없지만, __proto__라는 접근자 프로퍼티를 통해 간접적으로 접근할 수 있다. 

 

const user = {
  name: "kim",
};

console.log(user.hasOwnProperty("__proto__")); // false
console.log(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__"));
// {enumerable: false, configurable: true, get: ƒ, set: ƒ}

 

__proto__는 객체가 직접 가지고 있는 프로퍼티가 아니라, Object.prototype의 프로퍼티이다. 따라서 모든 객체는 상속을 통해 __proto__를 사용할 수 있다. 함수 객체의 prototype 프로퍼티로 프로토타입에 접근하는 것과 인스턴스의 __proto__ 접근자 프로퍼티로 프로토타입에 접근하는 것은 같다.

 

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

const user1 = new User("kim");

console.log(User.prototype === user1.__proto__); // true

 

 

# 프로토타입 생성 시점

 

 Object, Array, String, Number, Function, Date, Promise, RegExp 등과 같은 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 프로토타입이 생성된다. 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다. 위 예시에서 살펴본 Array.prototype.map()이 이 경우에 해당한다.

 

 사용자 지정 생성자 함수의 경우에는 함수 객체를 생성하는 시점에 프로토타입이 생성된다. 생성자 함수는 함수 선언문 형식으로 지정되므로, 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되고 이때 프로토타입도 생성된다.

 

 

# 프로토타입 체인

 

 자바스크립트는 객체의 프로퍼티에 접근하려 할 때, 해당 객체에 접근하려는 프로퍼티가 존재하지 않으면 [[Prototype]] 내부 슬롯의 참조를 따라 상위 객체의 프로토타입의 프로퍼티를 검색한다. 이를 프로토타입 체인이라고 한다.

 

 

# 오버라이딩과 프로퍼티 섀도잉

 

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

User.prototype.sayHello = function () {
  return `hello, ${this.name}!`;
};

const user1 = new User("kim");

console.log(user1.sayHello()); // hello, kim!

user1.sayHello = function () {
  return `hello, my name is ${this.name}!`;
};

console.log(user1.sayHello()); // hello, my name is kim!

 

 프로토타입의 프로퍼티와 같은 이름의 함수를 인스턴스에 추가했다. 하지만 프로토타입의 프로퍼티에 덮어쓰는 것이 아니라 인스턴스의 프로퍼티로 추가한다. 상위 클래스가 가지고 있는 메서드를 하위 클래스에서 재정의해서 사용한 것이다. 이것을 오버라이딩이라고 한다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려지는데, 이를 프로퍼티 섀도잉이라고 한다.

 


 

참고 자료

책 '모던 자바스크립트 Deep Dive' 19장

책 '코어 자바스크립트' 6장

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map

http://www.tcpschool.com/javascript/js_object_prototype

'FE > JavaScript' 카테고리의 다른 글

[JavaScript] 클로저  (0) 2023.03.27
[JavaScript] this  (0) 2023.03.26
[JavaScript] 객체지향과 Class 함수  (0) 2023.03.14
[JavaScript] 자주 사용하는 자바스크립트 코드 정리  (1) 2023.03.07
[JavaScript] 이벤트 루프  (0) 2023.02.21