Study/Javascript

프로토타입

kdhoooon 2022. 5. 21. 16:30

객체지향 프로그래밍

객체지향이란?

객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 노리적인 단위로 묶은것을 말함

 

객체란?

속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조

const circle = {
  redius: 5, //반지름

  //원의 지름: 2r
  getDiameter(){
      return 2 * this.readius;
  },

  //원의 둘레
  getPerimeter(){
      return 2 * Math.PI * this.radius;
  },

  //원의 넓이
  getArea(){
      return Math.PI * this.radius ** 2;
  }
};

console.log(circle);

console.log(circle.getDiameter());
console.log(circle.getPerimeter());
console.log(circle.getArea());

코드에서 circle 객체에서 redius 는 객체의 상태를 나타내고, getDiameter(), getPerimeter(), getArea() 를 구하는 것은 동작을 나타낸다.

 

 

상속과 프로토 타입

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);

console.log(circle1.getArea === circle2.getArea); //?

console.log(circle1.getArea());
console.log(circle2.getArea());

위 코드는 생성자 함수를 이용해서 생성한 경우

 

위 코드의 문제점은 아래 그림과 같이 

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);

console.log(circle1.getArea === circle2.getArea); //?

console.log(circle1.getArea());
console.log(circle2.getArea());

자바스크립트는 프로토타입(prototype)을 기반으로 상속을 구현한다. 위 코드는 이를 이용하여 작성한 코드다.

Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입, 즉 상위 객체 역할을 하는 Circle prototyp의 모든 프로퍼티와 메서드를 상속받음

 

이렇게 할 경우 아래의 사진과 같이 getArea 메서드를 프로퍼티를 통해 상속받아서 사용하기 때문에 동일한 메서드가 중복 생성되는 것을 방지 할 수 있다.

프로토타입 객체

  • 모든 객체는 하나의 프로토타입을 갖고, 모든 프로토타입은 생성자 함수와 연결되어있다.

객체와 프로토타입과 생성자 함수는 서로 연결돼 있다

__proto__  접근자 프로퍼티

__proto__ 를 이용하여 접근할 수 있다.

const obj = {};
const parent = { x : 1 };

obj.__proto__;
obj.__proto__ = parent;
console.log(obj.x);

위의 코드처럼 __proto__ 접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 __proto__ 접근자 프로퍼티의 setter 함수인 [[Set]]이 호출 됨

 

 

 

사용이유?

const parent = {};
const child = {};

child.__proto__ = parent;
parent.__proto__ = child; //?
  • 위 코드에서와 같이 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서 사용한다.
  • 서로가 자신의 프로토타입이 되는 비정상적인 프로토타입 체인이 만들어지기 때문에 에러를 발생시킨다.
  • 따라서 프로토타입 체인은 단방향 링크드 리스트로 구현돼야 한다.

서로가 자신의 프로토타입이 되는 비정상적인 프로토타입 체인

 

__proto__ 접근자 프로퍼티를 코드내에서 직접사용하는것은 권장하지 않는다.

  • 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용
  • 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용할 것
const obj = {};
const parent = { x : 1 };

// obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); // oboj.__proto__;
// obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj,parent); // obj.__proto__ = parent

console.log(obj.x);

 

함수 객체의 prototype 프로퍼티

  • 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
  • non-constructor 인 화살표 함수, 메서드 축약표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며, 생성하지도 않음
const Person = name => {
  this.name = name;
};

console.log(Person.hasOwnProperty('prototype')); //?

console.log(Person.prototype); //?

const obj = {
  foo() {}
};

console.log(obj.foo.hasOwnProperty('prototype')); //?

console.log(obj.foo.prototype); //?

 

모든 객체가 가지고 있는(Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킴

구분 소유 사용주체 사용목적
__proto__ 접근자 프로퍼티 모든 객체 프로토타입의 참조 모든 객체 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용
prototype 프로퍼티 constructor 프로토타입의 참조 생성자 함수 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로
토타입을 할당하기 위해 사용

모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결은 생성자 함수가 생성될 때 이뤄진다.

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

const me = new Person('Lee');
console.log(Person.prototype === me.__proto__); //?
console.log(me.constructor === Person); //?

 

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

  • 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재, 하지만 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수 없음
const obj = {};

console.log(obj.constructor === Object);

 

Object 생성자 함수

  • 리터럴로 생성된 객체가 위의 코드를 보면 Object 생성자 함수와 constructor 프로퍼티로 연결 돼 있다. 이를 확인해 보기위해 ECMAScript 정의한 Objectt 생성자 함수를 살펴보면
  • 2 에서 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로 추상 연산(OrdinaryObjectCreate)를 호출하여 Object.prototype을 프로토타입으로 갖는 빈객체를 생성
//1. new.target이 undefined나 Object가 아닌 경우
//인스턴스 -> Foo.prototype -> Object.prototype 순으로 프로토타입 체인이 생성됨
class Foo extends Object{}
new Foo();

//2. Object 생성자 함수에 의한 객체 생성
//인수가 전달되지 않았을 때 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성
let obj = new Object();
console.log(obj);

//3. 인수가 전달된 경우에는 인수를 객체로 변환
//Number 객체 생성
obj = new Object(123);
console.log(obj);

객체 리터럴의 평가

  • Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일
  • 하지만, new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부내용이 다름
  • 따라서 객체리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님
  • 함수 객체의 경우에 함수 선언문과 함수 표현식을 평가하여 함수 객체를 생성한 경우 Function 생성자 함수가 아니지만, 상속을 위해서 프로토타입이 필요하기 때문에 가상적인 생성자 함수를 갖음
  • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재함
리터럴 표기법 생성자 함수 프로토타입
객체 리터럴 Object Object.prototype
함수 리터럴 Function Function.prototype
배열 리터럴 Array Array.prototype
정규 표현식 리터럴 RegExp RegExp.prototype

 

프로토타입의 생성 시점

  • 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성
  • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하기 때문

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

  • 생성자 함수로서 호출할 수 있는 함수, 즉 constructor 는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불서 생성
  • 생성된 프로토타입은 오직 constructor 프로퍼티만을 갖는 객체, 프로토타입도 객체고 모든 객체는 프로토타입을 가지므로 프로토타입도 자신의 프로토타입을 갖는다.
  • 생성된 프로토타입의 프로토타입은 Object.prototype
function Person(name){
    this.name = neme;
}

const person = name => {
    this.name = name;
};

console.log(Person.prototype); // {constructor: f}
console.log(person.prototype); // undefined

생성자 함수와 더불어 생성된 프로토타입
생성된 프로토타입의 프로토타입

 

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

  • Object, String, Number, Function, Array, RegExp, Date, Promise 등과 같은 빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성 됨
  • 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성
  • 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩 됨
  • 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨
전역 객체

 

  • 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 생성되는 특수한 객체
  • 어떤 객체보다도 먼저 생성되는 특수한 객체이며, 어떤 객체에도 속하지 않는 최상위 객체
  • 브라우저 환경에서는 winodw(또는 self, this, frames)가 전역 객체를 가리키고, Node.js 환경에서는 global이 전역 객체를 가리킴.
  • 표준 빌트인 객체(Object, String, Number, Function, Array 등)와 환경에 따른 호스트 객체(클라이언트 Web API 또는 Node.js의 호스트 API), 그리고 var 키워드로 선언한 전역 변수와 전역함수를 프로퍼티로 갖음
  • 객체가 최상위 객체라는 것은 프로토타입 상속 관계상에서 최상위 객체라는 의미가 아님
  • 자신은 어떤 객체의 프로퍼티도 아니고 객체의 계층적 구조상 표준 빌트인 객체와 호스트 객체를 프로퍼티로 소유함을 의미