기술부채

JavaScript의 Prototype

생각하는렁이G 2020. 7. 29. 20:06

Prototype이란?

[자바스크립트의 기본 타입]

기본타입   참조타입
Number
(Number.prototype)
  Object  
String
(String.prototype)
    Array
(Array.prototype)
Boolean     Function
(Function.prototype)
undefined     정규표현식
null      

 

var foo = {
  name: 'foo',
  age:30
};

console.log(foo.tostring());

기본 객체를 생성하면 자신이 선언하지 않은 메소드toString을 호출할 수 있다.

foo객체의 프로토타입인 Object.prototype객체에 포함된 다양한 메서드를 마치 자신의 프로퍼티인 것처럼 상속받아 사용할 수 있다.

 

[프로토타입 정의]
모든 객체는 자신의 부모역할을 하는 프로토타입 객체를 가리키는 숨겨진 프로퍼티가 있다

- Object.prototype - __proto__ 모든 객체의 부모

- Array : Array.prototype - __proto__

- Fucntion : Function.prototype

모든 객체에 있는 내부 프로퍼티 __proto__과 함수 객체가 가지는 prototype 프로퍼티는 다르다.

함수의 prototype 프로퍼티는 이 함수가 생성자가로 사용될 때 이 함수를 통해 생성된 객체의 부모 역할을 하는 프로토타입 객체를 가리킨다.

function add(x, y){
  return x+y;
}
console.dir(add);  

add
(함수객체)
  add.prototype
(프로토타입 객체)
prototype 프로퍼티 <--------------
--------------->
constructor 프로퍼티

 

 

[객체 생성]

- 생성자함수가 생성한 객체는 자신을 생성한 생성자 함수의 prototype프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정

- new 로 호출되면 생성자함수는 new Person이면 Person이 프로토타입 객체가 된다.

- 객체 리터럴 방식이면 Object가 프로토타입 객체가 된다.

- return값이 특별히 없으면 this로 바인딩된 새로 생성한 객체가 리턴

- 생성자함수를 new로 붙이지 않으면 안의 this는 window 객체이다.

위의 문제 해결 방법. (Object.create와 비슷)

function A(arg){
  if (!(this instanceof A))
    return new A(arg);
  this.value = arg?arg:0;
}

var a = new A(100);
var b = A(10);

 

 

[프로토타입 체이닝]

- 자바와 같은 객체지향 프로그래밍에서는 클래스를 정의하고 이를 통해 객체를 생성하지만, 자바스크립트에서는 이러한 클래스 개념이 없다. 대신 객체 리터럴이나 앞서 설명했던 생성자 함수로 객체를 생성한다. 이렇게 생성된 객체의 부모 객체가 바로 '프로토타입' 객체다.

- 객체는 자기 자신의 프로퍼티 뿐만이 아니라, 자신의 부모역할을 하는 프로토타입 객체의 프로퍼티 또한 마치 자신의 것처럼 접근 가능

- 자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가르키는 [[Prototype]] 객체를 자신의 부모 객체로 설정하는 [[Prototype]] 링크로 연결

 

- 프로토타입 체이닝 : 리터럴 방식

: 객체는 자기 자신의 프로퍼티 뿐만이 아니라, 자신의 부모역할을 하는 프로토타입 객체의 프로퍼티 또한 마치 자신의 것처럼 접근 가능

: [[prototype]] 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것을 프로토타입 체이닝

 

- 프로토타입 체이닝 : 생성자함수

: 리터럴방식과 기본은 같은데 자신의 프로토타입객체에 연결하고 있는 프로토타입 객체까지 검색한다.

 

- Object.prototype 객체는 프로토타입 체이닝의 종점

 

 

https://programmer-seva.tistory.com/31

 

https://programmer-seva.tistory.com/31

- 프로토타입 객체는 디폴트로 constructor 프로퍼티만 가진 객체다. 당연히 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다.

 

- 값을 쓸때는 프로토타입 체이닝이 발생하지 않음. 왜냐하면 객체에 없는 프로퍼티에 값을 쓰려고 할 경우 동적으로 객체에 프로퍼티를 추가하기 때문

 

Prototype의 활용

- 공유할 수 있는 메소드는 prototype에 저장

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

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

var me = new Person("me");
var you = new Person("you");

- 상속

function create_object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var person = {
  name : "zzoon",
  getName : function() {
    return this.name;
  },
  setName : function(arg) {
    this.name = arg;
  }
};

var student = create_object(person);
function Person(arg) {
  this.name = arg;
}
Function.prototype.method = function(name, func) {
  this.prototype[name]= func;
}
Person.method("setName", function(value){
  this.name = value;
});
Person.method("getName", function(){
  return this.name;
});
function Student(arg){
}

var inherit = function(Parent, Child){
  var F = function() {};
  return function(Parent, Child){
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.super = Parent.prototype;
  };
}

var me = new inherit(Person, Student);
me.setName("zzoom");

 

TypeScript로 상속 만든 것

부모

exports.DrawingTool = void 0;
var DrawingTool = /** @class */ (function () {
    function DrawingTool(marker, eventObserver, attr, saveDrawing, isCell) {
        var _this = this;
        this.getBox = function (l, t, w, h) {
            return {};
        };
........
    }
    DrawingTool.prototype.setHeight = function (info, height) {
        h = height;
    };
    }
    return DrawingTool;
}());
exports.DrawingTool = DrawingTool;
    

 

자식

 

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
exports.DrawingMemo = void 0;
var DrawingMemo = /** @class */ (function (_super) {
    __extends(DrawingMemo, _super);
    function DrawingMemo(marker, eventObserver, attr, saveDrawing, isCell) {
        var _this = _super.call(this, marker, eventObserver, attr, saveDrawing, isCell) || this;
        _this.mClick = function (selection, elem) {
        };
..............        
        return _this;
    }
    return DrawingMemo;
}(DrawingTool_1.DrawingTool));
exports.DrawingMemo = DrawingMemo;
        

 

 

 

기타

- 비표준 __proto__ 을 사용하지 말고 getPrototypeOf()를 사용한다. ES5이상

- __proto__ 직접 수정하지 마라. Object.create를 이용해서 임의로 지정할 수 있는 프로토타입을 따로 만들어라.

- prototype에 인스턴스 상태를 저장하지 말고, 인스턴스 객체에만 저장하라.

보통 수정 불가능한 데이터를 prototype에 공유하는 것은 안전하며, 의도적인 상태값을 괜찮지만 인스턴스마다 달라지는 상태값은 인스턴스 객체 저장되어야 한다.

 

 

참고자료

인사이드 자바스크립트 : 코드는 대부분 인사이드 자바스크립트 예제입니다.