Deep Dive 19장 프로토타입(1)

@hanminss·2021년 12월 6일
0

Deep Dive

목록 보기
11/16
post-thumbnail
  • 자바스크립트는 프로토타입 기반의 객체지향 언어이다.
  • 자바스크립트를 이루고 있는 거의 모든것은 객체이다.

1. 객체지향 프로그래밍

명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임

  • 객체는 특징이나 성질을 나타내는 속성을 가지고 있고, 이를 통해 실체를 인식하거나 구별할 수 있다.
  • 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하려는 것을 추상화 라고 한다.
  • 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조이다.

2. 상속과 프로토타입

상속은 객체지향 프로그래밍의 핵심 개념으로, 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다.

  • 자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거한다.
function Circle(radius){
    this.radius = radius;
    this.getArea = function{
        return Math.PI * this.radius **2;
    }
}

const circle1 = new Circle(1);
const circle2 = new Circle(2);
  • 위 코드 중 getArea함수는 단 하나만 생성하여 모든 인스턴스가 공유하여 사용하는 것이 바람직 하지만 위 코드는 그렇지 않고 각 인스턴스가 동일한 메서드를 중복 소유하여 메모리를 낭비하고 있다.
  • 상속을 이용하여 중복을 제거해준다.
function Circle(radius) {
  this.radius = radius;
}

Circle.prototype.getArea = function () {
  return Math.PI * this.radius ** 2;
};

const circle1 = new Circle(1);
const circle2 = new Circle(2);
  • getArea 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩 된다.

3. 프로토타입 객체

  • [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정된다.
  • 객체 리터럴에 의해 생성된 객체의 프토로타입은 Object.prototype 이다.
  • 생성자 함수에 의해 생성된 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.
  • 모든 객체는 하나의 프로토타입을 갖는다.
  • 모든 프로토타입은 생성자 함수와 연결되어있다.

proto 접근자 프로퍼티

  • 모든 객체는 proto 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
  1. proto 는 접근자 프로퍼티이다.

    • proto 는 접근자함수 [[Get]],[[Set]]를 통해 [[Prototype]] 내부 슬롯의 값, 즉 프로토타입을 취득 하거나 할당한다.
  2. proto 접근자 프로퍼티는 상속을 통해 사용된다.

    • proto 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티이다.
    • 모든 객체는 상속을 통해 Object.property. proto 를 사용하는 것
    const person = { name: "Yim" };
    
    console.log(person.hasOwnProperty("__proto__")); // false
  3. proto 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유

    • 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해
      const parent = {};
      const child = {};
      
      child.__proto__ = parent;
      parent.__proto__ = child; // TypeError: Cyclic __proto__ value
      
    • 위 코드에서 상호 체인을 걸었더니 proto 에서 무한루프에 빠진다고 경고를 준다. 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 하기 때문이다.
    • 아무런 체크없이 무조건적으로 프로토타입을 교체할 수 없도록 proto 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현되었다.
  4. proto 를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

    • 모든 객체가 proto 를 사용할 수 있지 않기 때문
    • 따라서 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf(obj), 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf(obj, protoObj)를 사용할 것을 권장한다.
    const obj = {};
    const parent = { x: 1 };
    
    console.log(Object.getPrototypeOf(obj));
    console.log(obj.__proto__);
    //  [Object: null prototype] {} 둘의 결과는 같다.
    
    Object.setPrototypeOf(obj, parent);
    
    console.log(Object.getPrototypeOf(obj));
    // { x: 1 }

    함수 객체의 prototype 프로퍼티

    • 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
    • 생성자 함수로서 호출할수 없는 함수(화살표함수, 축약표현 메서드)는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않는다.
    • 모든 객체가 가지고 있는 proto 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.

    프로토타입의 constructor 프로퍼티와 생성자 함수

    • 모든 프로토타입은 costructor 프로퍼티를 갖는다.
    • 프로토타입의 constructor 프로퍼티는 프로토타입 프로퍼티로 자신을 참조하고 있는 함수를 가리킨다.
    • 이 연결은 생성자 함수가 생성될 때 이뤄진다.
    function Person(name) {
      this.name = name;
    }
    
    const me = new Person("Yim");
    
    console.log(me.constructor === Person); // true

    4. 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

    • 리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재한다.
    • 하지만 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없다.
    const obj = {};
    console.log(obj.constructor === Object); //true
    • obj는 생성자 함수로 생성한 객체가 아니라 객체 리터럴에 의해 생성된 객체다. 하지만 Object 생성자와 연결되어있다.

    Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null 을 인수로 전달하면서 호출하면 내부적으로는 추상 연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성한다. (ECMAScript 19.1.1.1 -2 )

    • Objecet 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일하나 new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용은 다르다.
    • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
    • 리터럴 표기법에 의해 생성된 객체는 생성자 함수에 의해 생성된 객체는 아니다.
    • 하지만 큰틀에서 보면 본질적인 큰 차이는 없다.

5. 프로토타입의 생성 시점

  • 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다. 프로토타입고 생성자 함수는 단독으로는 존재할 수 없고 언제나 쌍으로 존재하기 때문이다.
  • 생성자 함수는 사용자가 직접 정의한 사용자 정의 생성자 함수와 자바스크립트가 제공하는 빌트인 생성자 함수로 구분할 수 있다.

사용자 정의 생성자 함수와 프로토타입 생성 시점

  • 생성자 함수로서 호출할 수 있는 함수는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // 함수 호이스팅에 의해 함수가 호이스팅 되었고
// 이에 따라 프로토타입도 미리 생긴것을 볼 수 있다.

function Person(name) {
  this.name = name;
}
  • 생성된 프로토타입은 Person 생성자 함수의 prototype 프로퍼티에 바인딩된다.
  • 생성된 프로토타입은 오직 constructor 프로퍼티만을 갖는 객체이다.
  • 모든 객체는 프로토타입을 가지므로 프로토타입도 프로토타입을 가진다. 이는 언제나 Object.prototype이다.

빌트인 생성자 함수와 프로토타입 생성 시점

  • 빌트인 생성자 : Object, String, Number, Function,, Array, RegExp, Date, Promise 등
  • 빌트인 생성자 함수도 일반함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다.
  • 모든 빌트인 함수는 전역객체가 생성되는 시점에 생성된다.
  • 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩 된다.
  • 전역 객체 : 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 생성되는 특수한 객체, window(browser), global(server side) 객체가 된다.
  • 이는 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재함을 의미한다.

6. 객체 생성 방식과 프로토타입의 결정

  • 객체를 생성하는 방법은 여러가지가 있고 각 방식마다 세부적인 객체 생성 방식의 차이는 있으나 추상 연산 OrdinaryObjectCreate에 의해 성성된다는 공통점이 있다.
  • OrdinaryObjectCreate는 빈 객체를 생성한 후, 객체에 추가할 프로퍼티 목록이 인수로 전달된 경우 프로퍼티를 객체에 추가한다. 그 후 생성한 객체의 [[Prototype]] 내부 슬롯에 할당한 다음 생성한 객체를 반환한다.

객체 리터럴에 의해 생성된 객체의 프로토타입

  • 엔진은 객체 리터럴을 평가하여 객체를 생성할 때 추상 연산 OrdinaryObjectCreate을 호출한다.
  • 이때 추상연산 OrdinaryObjectCreate에 전달되는 프로토타입은 Object.prototype이다.

Object 생성자 함수에 의해 생성된 객체의 프로토타입

  • Object 생성자 함수를 인수없이 호출하면 빈 객체가 생성된다.
  • Object 생성자 함수를 호출하면 객체 리터럴과 마찬가지로 추상 연산 OrdinaryObjectCreate가 호출된다.
  • 이때 추상연산 OrdinaryObjectCreate에 전달되는 프로토타입은 Object.prototype이다.
  • 객체 리터럴과 Object 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식에 있다.

생성자 함수에 의해 생성된 객체의 프로토타입

  • new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate가 호출된다.
  • 이때 추상연산 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체이다.
  • 생성자 함수의 프로토타입에는 constructor 메서드만 존재한다.

0개의 댓글