새소식

프론트엔드 공부/자바스크립트

프로토타입(Prototype), 클래스, 인스턴스, 프로토타입의 관계

  • -

프로토타입(Prototype)이란?

  • 프로토타입은 원형 객체를 의미합니다.
  • 한국어로는 원형정도로 번역되는 prototype은 말 그대로 객체의 원형이라고 할 수 있다.
    함수는 객체다. 그러므로 생성자로 사용될 함수도 객체다. 객체는 프로퍼티를 가질 수 있는데 prototype이라는 프로퍼티는 그 용도가 약속되어 있는 특수한 프로퍼티다. prototype에 저장된 속성들은 생성자를 통해서 객체가 만들어질 때 그 객체에 연결된다. 
  • ECMA-262에서 prototype은 object that provides shared properties for other objects로, 다른 객체에 공유 프로퍼티(메서드 포함)를 제공하는 객체이다.
  • 정확히 말하자면 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 prototype이라는 속성에 정의되어 있다.
// ES5
function Ultra(){}
Ultra.prototype.ultraProp = true;
 
function Super(){}
Super.prototype = new Ultra();
 
function Sub(){}
Sub.prototype = new Super();
 
var o = new Sub();
console.log(o.ultraProp); // --> true

// ES6 
class Ultra {
    constructor() {}
}
Ultra.prototype.ultraProp = true;

class Super extends Ultra {
    constructor() {
        super();
    }
}

class Sub extends Super {
    constructor() {
        super();
    }
}

const o = new Sub();
console.log(o.ultraProp); // --> true
  1. 위 코드에서 Ultra 클래스는 프로토타입 객체를 통해 ultraProp 속성을 정의하였고,
  2. Super 클래스는 Ultra 클래스를 상속받아 ultraProp 속성을 상속받아 사용할 수 있다.
  3. 그리고 Sub 클래스는 Super 클래스를 상속받아 ultraProp 속성을 상속받아 사용할 수 있다.
  4. 그리고 o 객체는 Sub 클래스를 인스턴스화 하여 ultraProp 속성을 사용할 수 있다.

모든 객체는 [[Prototype]]이라는 내부 슬롯(자바스크립트 엔진의 내부 로직)을 갖으며, 상속을 구현하는 프로토타입 객체를 가르킨다.

모던 자바스크립트 Deep Dive, 이웅모

하지만 [[Prototype]] 내부 슬롯에는 직접 접근이 불가하다. 이는 프로토타입 체인의 단방향을 지키기 위해서다. 만약 직접 접근가능하다면, 서로가 서로의 프로토타입이 되면서 프로토타입 체인이 무한으로 돈다. 따라서 __proto__ 프로퍼티로만 접근할 수 있다.

const a = {};
const b = {};

a.__proto__ = b;
b.__proto__ = a; // Uncaught TypeError: Cyclic __proto__ value

.prototype(함수의 prototype 프로퍼티)

  • prototype 프로퍼티를 이용하면 현재 존재하고 있는 프로토타입에 새로운 프로퍼티나 메소드를 손쉽게 추가할 수 있다.
  • 과거엔 프로토타입에 직접 접근할 수 있는 방법이 없었다. 그나마 믿고 사용할 수 있었던 방법은 이번 챕터에서 설명할 생성자 함수의 "prototype" 프로퍼티를 이용하는 방법뿐..
  • 생성자 함수의 프로토타입이 객체인 경우에 new 연산자를 사용해 만든 객체는 생성자 함수의 프로토타입 정보를 사용해 [[Prototype]]을 설정한는 것이다.
  • 생성자 함수에 기본으로 세팅되는 프로퍼티(F.prototype)는 [[Prototype]]과 다르다. F.prototype은 new F()를 호출할 때 만들어지는 새로운 객체의 [[Prototype]]을 설정합니다.
  • F.prototype의 값은 객체나 null만 가능합니다. 다른 값은 무시된다.
  • 생성자 함수를 new를 사용해 호출할 때만 적용됩니다. 

프로토타입 체인을 이용한 상속

 

상속과 프로토타입 - JavaScript | MDN

Java 나 C++ 같이 클래스 기반의 언어를 사용하던 프로그래머는 자바스크립트가 동적인 언어라는 점과 클래스가 없다는 것에서 혼란스러워 한다. (ES2015부터 class 키워드를 지원하기 시작했으나,

developer.mozilla.org

프로토타입 체인은 쉽게 생각해서 부모요소에 적용된 것들을 자식요소가 상속받아 자식요소에서도 사용할 수 있다고 생각하면 편할것 같다.

.__proto__    // 더 이상 사용되지 않음  자세히 보기

 

Object.prototype.__proto__ - JavaScript | MDN

주의: 객체의 [[Prototype]]을 변경하는 것은 최신 JavaScript 엔진이 속성 접근을 최적화하는 방식의 특성상 모든 브라우저 및 JavaScript 엔진에서 매우 느린 작업입니다. 상속 구조를 변경하는 것이 성

developer.mozilla.org

  • 모던 브라우저들이 __proto__ (앞뒤로 언더바 2개씩) 속성을 통해 특정 객체의 프로토타입 객체에 접근할 수 있도록 구현하였다.
  • 모든 객체는 __proto__를 통해 자신의 프로토타입([[Prototype]] 내부 슬롯)에 접근할 수 있다.
  • ECMAScript 2015부터는 Object.getPrototypeOf(obj) 함수를 통해 객체의 프로토타입 객체에 바로 접근할 수 있게 되었습니다.
  • ES6에서 __proto__를 표준으로 채택되었다.  (사용은 지양한다)
  • Object.prototype.__proto__는 오늘날 대부분의 브라우저에서 지원되지만 그 존재와 정확한 동작은 웹 브라우저와의 호환성을 보장하기 위해 레거시 기능으로 ECMAScript 2015 사양에서만 표준화되었다. 더 나은 지원을 위해 대신 Object.getPrototypeOf()를 사용을 권장한다.
  • __proto__은 논란의 여지가 있으며 권장되지 않는다. 그 존재와 정확한 동작은 웹 호환성을 보장하기 위해 레거시 기능으로만 표준화되었지만 몇 가지 보안 문제가 있다. 더 나은 지원을 위해 Object.getPrototypeOf()/ Reflect.getPrototypeOf() Object.setPrototypeOf()/ Reflect.setPrototypeOf()를 대신 사용하길 권장한다.
const hello = { name: '김코딩' };
Object.getPrototypeOf(hello);
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

.__proto__는 객체의 프로토타입을 가리키는 프로퍼티입니다. 객체는 프로토타입 객체를 가리키는 링크를 가지고 있으며, 프로토타입 객체는 자신의 프로퍼티를 가지고 있다.

예를 들어, 아래와 같이 객체를 정의할 수 있다.

Copy code
let myObject = { a: 1, b: 2 };

이 객체는 Object.prototype 객체를 프로토타입으로 가르킨다. 따라서, myObject.__proto__는 Object.prototype을 가르킨다.

또한, 객체의 프로퍼티를 정의할 때 프로토타입 객체의 프로퍼티를 상속받을 수 있다. 예를 들어,

Copy code
let anotherObject = Object.create(myObject); 
console.log(anotherObject.__proto__ === myObject); // true 
console.log(anotherObject.a); // 1

anotherObject는 myObject를 프로토타입으로 가르킨다. 그리고 myObject의 프로퍼티 a를 상속받아 사용할 수 있다.

// div의  .__proto__  값 확인시

let div = document.createElement('div');
//undefined

div.__proto__ 
//HTMLDivElement {Symbol(Symbol.toStringTag): 'HTMLDivElement', onmouseenter: undefined, onmouseleave: undefined, constructor: ƒ}

div.__proto__.__proto__
//HTMLElement {…}

div.__proto__.__proto__.__proto__
//Element {…}

div.__proto__.__proto__.__proto__.__proto__
//Node {…}

div.__proto__.__proto__.__proto__.__proto__.__proto__
//EventTarget {Symbol(Symbol.toStringTag): 'EventTarget', addEventListener: ƒ, dispatchEvent: ƒ, removeEventListener: ƒ, constructor: ƒ}

div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}constructor: ƒ Object()hasOwnProperty: ƒ hasOwnProperty()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toLocaleString: ƒ toLocaleString()toString: ƒ toString()valueOf: ƒ valueOf()__defineGetter__: ƒ __defineGetter__()__defineSetter__: ƒ __defineSetter__()__lookupGetter__: ƒ __lookupGetter__()__lookupSetter__: ƒ __lookupSetter__()__proto__: (...)get __proto__: ƒ __proto__()set __proto__: ƒ __proto__()

div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__
//null

 

클래스, 인스턴스, 프로토타입의 관계

  • 클래스는 객체를 생성하는 틀, 프로토타입을 정의하는 공장 역할을 한다.
  • 인스턴스는 클래스로부터 생성된 객체를 의미한다. 인스턴스는 클래스에서 정의된 프로퍼티와 메서드를 상속받아 사용할 수 있다.
  • 프로토타입은 클래스나 인스턴스가 공유하는 기능을 정의하는 객체다. 인스턴스는 클래스로부터 상속받은 프로퍼티와 메서드를 프로토타입에서 상속받아 사용할 수 있다.

정리하자면, 클래스는 객체를 생성하는 틀을 정의하며 인스턴스는 클래스로부터 생성된 객체를 의미하며 프로토타입은 클래스나 인스턴스가 공유하는 기능을 정의하는 객체다.

class MyClass {
    constructor() {
        this.property = "I'm an instance property";
    }
    method() {
        console.log("I'm an instance method");
    }
}
MyClass.staticProperty = "I'm a static property";
MyClass.staticMethod = function () {
    console.log("I'm a static method");
}

const instance = new MyClass();
console.log(instance.property); // "I'm an instance property"
instance.method(); // "I'm an instance method"
console.log(MyClass.staticProperty); // "I'm a static property"
MyClass.staticMethod(); // "I'm a static method"

위 코드에서 MyClass는 클래스로써 객체를 생성할 수 있도록 정의하였다.

propertymethod는 인스턴스의 프로퍼티와 메서드를 의미하며 staticPropertystaticMethod는 클래스의 프로퍼티와 메서드를 의미한다. 그리고 instanceMyClass 클래스로부터 생성된 인스턴스를 의미하며 MyClass 클래스로부터 상속받은 프로퍼티와 메서드를 사용할 수 있다.


JavaScript에서 언제 상속을 사용해야 할까?

 

Classes in JavaScript - Web 개발 학습하기 | MDN

OOJS에 대한 개념을 설명했으니, 이 글에서는 부모 클래스에서 자식 클래스를 상속하는 방법을 알아봅니다. 덤으로 OOJS를 구현하는데 몇 가지 참고사항도 있습니다.

developer.mozilla.org

프로토타입과 상속은 Javascript에서 가장 난해한 부분, 어찌보면 여러분은 항상 상속하고 있었습니다. Web API나 브라우저 내장 객체인 string, array 등의 메소드/속성을 사용하면서 암묵적으로 상속을 사용하고 있었던거죠.

 

 

참조:
https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object_prototypes
https://poiemaweb.com/js-prototype
https://ko.javascript.info/function-prototype
http://www.tcpschool.com/javascript/js_object_prototype
https://www.howdy-mj.me/javascript/prototype-and-proto

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.