·자바스크립트는 프로토타입 기반 언어

  - 클래스 기반 언어는 상속을 사용(프로그래밍 언어 대부분이 클래스 기반)

  - 프로토타입 기반 언어는 어떤 객체를 원형으로 삼고, 이를 복제(참조)하여 상속과 비숫한 효과를 얻음

 

1. 프로토타입의 개념 이해

 1) constructor, prototype, instance의 관계

  - 코드와 코드를 도식화한 그림

var instance = new Constructor();

  - 생성자 함수 Constructor()를 new 연산자와 함께 호출

  - Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스(var instance)가 생성됨

  - instance에는 __proto__라는 프로퍼티가 자동으로 부여되며, 이 프로퍼티는 Constructor의 prototype 프로퍼티를 참조

  - prototype은 객체이며, 이를 참조하는 __proto__ 또한 객체

  - prototype 내부에는 instance가 사용할 메서드 저장, instance에서도 __proto__를 통해 이 메서드들에 접근 가능

  - __proto__를 사용한 접근 대신 Object.getPrototypeOf(instance) 또는 Refelect.getPrototypeOf(instance)를 통해 접근 권장

 

  - 예시

// Person이라는 생성자 함수
var Person = function (name) {
  this._name = name;
};

// Person의 prototype에 getname이라는 메서드 지정
Person.prototype.getName = function() {
  return this._name;
};

// 생성자 함수 Person을 통해 새로운 인스턴스 name을 생성
var name = new Person('name');

// Person의 인스턴스 name은 __proto__프로퍼티를 통해 getName 메서드 호출 가능
name.__proto__.getName();  // undefined

// 인스턴스의 __proto__는 Person의 prototype을 참조하므로, 결국 둘은 같은 객체를 바라봄
Person.prototype = name.__proto__  // true

  - name.__proto__.getName()이 undefined로 뜬 것은 getName()함수가 this._name을 반환하는데 this._name이 없기 때문

  - 메서드의 this는 메서드 바로 앞의 객체이므로 이때 this는 name.__proto__

  - 따라서, name.__proto__에 _name 프로퍼티를 지정해주면 정상적으로 getName()함수가 실행될 것

var name = new Person('name');
name.__proto__._name = 'NAME__proto__';
name.__proto__.getName();  // NAME__proto__

 

  - __proto__는 생략 가능한 프로퍼티

var name1 = new Person('name1', 25);
name1.getName();   // name1
var name2 = new Person('name2', 25);
name2.getName();   // name2

  - __proto__를 생략하고 메서드를 호출하면 메서드(getName())의 this는 name.__proto__가 아닌 name이 되어 바로 name._name을 return할 수 있음

  - 도식화하면 다음과 같이 됨

 

  - prototype과 __proto__

var Constructor = function (name) {
  this.name = name;
};
Constructor.prototype.method1 = function() {};
Constructor.prototype.property1 = 'Constructor Prototype Property';

var instance = new Constructor('Instance');
console.dir(Constructor);
console.dir(instance);

  - Constructor의 디렉터리 구조

    → f Constructor(name): Constructor라는 이름의 함수, name을 인자로 가진다는 의미

    → 옅은 색의 arguments, caller, length, name, prototype은 열거할 수 없는(접근할 수 없는) 프로퍼티

    → 짙은 색의 method1, property1은 직접 지정하였고, 열거할 수 있는 프로퍼티

  - instance의 디렉터리 구조

    →Constructor가 가장 먼저 출력되어 생성자 함수 Consturctor에 의해 생성된 것임을 명시

    → [[Prototype]]은 __proto__와 같은 것으로, 이는 Constructor.prototype을 참조하므로 같은 내용을 출력

 

  - 내장 생성자 함수 Array

var arr = [1, 2];
console.dir(arr);
console.dir(Array);

arr의 디렉터리 구조
Array의 디렉터리 구조

  - arr의 디렉터리 구조

    → Array(2): length가 2인 Array

    → 인덱스 0과 1이 짙은 색으로, 접근 가능하다는 것을 의미

    → length는 옅은 색

    → [[Prototype]](=__proto__): 옅은 색상의 다양한 메서드(원래 배열에 사용하는 다양한 메서드들이 출력되어 있음)

  - Array의 디렉터리 구조

    → 함수라는 의미의 f

    → 함수의 기본적인 프로퍼티인 concat 등이 출력됨

    → prototype을 열면 arr의 __proto__와 같은 메서드들이 출력됨

Array와 arr의 디렉터리 구조 도식화

  - Array의 prototype 내부에 있는 메서드들에는 Array 생성자 함수를 통해 생성된 arr 같은 인스턴스도 얼마든지 접근 가능

  - Array의 prototype 외부에 있는 from, isArray 등의 메서드에는 인스턴스(arr)가 직접 호출할 수 없고 Array 생성자 함수에서 직접 접근해야 실행 가능

var arr = [1, 2];
arr.forEach(function (){});  // (0), forEach는 Array의 prototype 내부에 존재하므로 인스턴스인 arr도 사용가능
Array.isArray(arr);             // (0) true, isArray는 Array의 메서드이므로 Array가 사용가능
arr.isArray();                    // (x) TypeError: arr.isArray is not a function, isArray는 Array의 prototype 외부에 있으므로 인스턴스인 arr은 사용 불가능

 

 

 2) constructor 프로퍼티

  - 생성자 함수의 prototype 내부에는 constructor라는 프로퍼티가 항상 존재

  - 생성자 함수의 prototype을 참조하는 인스턴스의 __proto__에도 동일하게 constructor 프로퍼티가 항상 존재

  - constructor 프로퍼티는 원래의 생성자 함수를 참조

  - 인스턴스로부터 그 원형이 무엇인지 알기 위해 사용

var arr = [1, 2];
Array.prototype.constructor === Array
arr.__proto__.constructor === Array
arr.constructor === Array  // __proto__는 생략 가능한 프로퍼티

var arr2 = new arr.constructor(3, 4);
console.log(arr2);   // [3, 4]

  - arr 인스턴스는 Array라는 생성자 함수로부터 생성되었으며, constructor 프로퍼티를 통해 이를 확인 가능

  - arr.constructor는 생성자 함수 Array를 가리키므로 이를 활용해 새로운 인스턴스를 생성할 수도 있음

 

  - constructor는 읽기 전용 속성이 부여된 Number, String, Boolean을 제외하고는 값을 바꿀 수 있음

var NewConstructor = function() {
  console.log('this is new constructor!');
};

var dataTypes = [
  1,                              // Number & false
  'test',                         // String & false
  true,                           // Boolean & false
  {},                             // NewConstructor & false
  [],                             // NewConstructor & false
  function(){},             // NewConstructor & false
  /test/,                       // NewConstructor & false
  new Number(),          // NewConstructor & false
  new String(),             // NewConstructor & false
  new Boolean(),          // NewConstructor & false
  new Object(),            // NewConstructor & false
  new Array(),             // NewConstructor & false
  new Function(),        // NewConstructor & false
  new RegExp(),         // NewConstructor & false
  new Date(),             // NewConstructor & false
  new Error()             // NewConstructor & false
];

dataTypes.forEach(function(d) {
  d.constructor = NewConstructor;
  console.log(d.constructor.name, '&', d instanceof NewConstructor);
});

    → Number인 1, String인 'test', Boolean인 true는 d.constructor = NewConstructor에 의해 값이 바뀌지 않음

    → 다른 데이터타입들은 NewConstructor로 constructor가 변경됨

    → d instanceof NewConstructor는 d가 NewConstructor라는 생성자 함수에 의해 생성된 것인지 여부를 출력

    → d instanceof NewConstructor가 전부 false로 나왔으므로, constructor가 변경되어도 데이터 타입이나 인스턴스의 원형(인스턴스를 생성한 생성자 함수)이 바뀌는 것은 아님

 

  - 예시

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

var p1 = new Person('사람1');
var p1Proto = Object.getPrototypeOf(p1);
var p2 = new Person.prototype.constructor('사람2');
var p3 = new p1Proto.constructor('사람3');
var p4 = new p1.__proto__.constructor('사람4');
var p5 = new p1.constructor('사람5');

[ p1, p2, p3, p4, p5 ].forEach(function (p) {
  console.log(p, p instanceof Person);
});

// { name: "사람1"} true
// { name: "사람2"} true
// { name: "사람3"} true
// { name: "사람4"} true
// { name: "사람5"} true

    → p1은 생성자 함수([Constructor])인 Person에 의해 직접 생성

    → p2를 생성한 Person.prototype.constructor는 생성자 함수 자기 자신의 constructor, 즉 생성자 함수([Constructor])를 가리킴

    → p3를 생성한 p1Proto는 Object.getPrototypeOf(p1)은 p1의 __proto__와 같고 p1.__proto__.constructor는 p1을 생성한 생성자 함수([Constructor])를 가리킴

    → p4를 생성한 p1.__proto__.constructor는 위와 같은 의미

    → p5를 생성한 p1.constructor는 위에서 생략 가능한 프로퍼티인 __proto__를 생략한 것

 

  - 정리하면, 다음 각 줄은 모두 같은 대상을 가리킴

[Constructor]
[instance].__proto__.constructor
[instance].constructor
Object.getPrototypeOf([instance]).constructor
[Constructor].prototype.constructor

  - 또한, 다음 각 줄은 모두 같은 객체(prototype)에 접근 가능

[Constructor].prototype
[instance].__proto__
[instance]
Object.getPrototypeOf([instance])

+ Recent posts