Prototype 빠개기

movie·2022년 9월 19일
2
post-thumbnail

🔪

자바스크립트는 프로토타입 기반의 객체지향 프로그래밍 언어이다.

🍪 Why prototype?

객체지향 프로그래밍

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

  • 실세계의 사물을 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작

상속

  • 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있게 하는 것
  • 상속은 불필요한 중복을 제거한다. (코드를 재사용할 수 있기 때문에 중복을 제거한다. 코드 재사용은 개발 비용을 줄인다.)

생성자 함수의 문제

생성자 함수는 동일한 속성 혹은 메서드를 객체마다 중복 생성한다. 중복 소유는 메모리를 불필요하게 낭비하기 때문에 동일한 속성과 메서드라면 공유해서 사용하는 것이 바람직하다.

이러한 불필요한 중복을 제거하기 위해 자바스크립트는 프로토타입을 기반으로 상속을 구현한다.

프로토타입에 추가한 속성 혹은 메서드는 모든 인스턴스가 공유한다. (모든 인스턴스가 속성 혹은 메서드를 상속받아 사용할 수 있다.)

즉, 자신의 상태를 나타내는 프로퍼티만 개별적으로 소유하고 내용이 동일한 프로퍼티는 상속을 통해 공유해 사용한다.

🍪 프로토타입 객체 (=prototype)

(줄여서 프로토타입이라고 한다.)

  • 객체 간 상속을 구현하기 위해 존재
  • 어떤 객체의 부모 객체 역할을 하는 객체
    • 자식 객체에게 공유 프로퍼티를 제공한다.
  • 생성자 함수(함수 객체)가 생성되는 시점에 더불어 생성된다.

[[Prototype]]

IMG_ACF4A584177F-1

  • 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가진다.
  • [[Prototype]]의 값은 프로토타입의 참조다.
  • 프로토타입은 객체 생성 방식에 의해 결정된다.
  • 모든 프로토타입은 생성자 함수와 연결되어 있다.
  • _ proto _ 접근자 프로퍼티를 통해 [[Prototype]]이 가르키는 프로토타입에 간접적으로 접근할 수 있다.

사용 목적 : 객체가 자신의 프로포타입에 접근 또는 교체하기 위해서 사용

왜 [[Prototype]]은 접근자 프로퍼티(_ proto _)를 통해 접근해야할까?

만약 parent 객체를 child 객체의 프로토타입으로 지정하고, child 객체를 parent 객체의 프로토타입으로 지정하면 서로가 자신의 프로토타입이 되는 비정상 프로토타입 체인이 만들어진다. 이때 프로토타입 체인 종점이 존재하지 않기 때문에 프로퍼티를 검색할 때 무한루프에 빠지게 된다. 이런 이유로 아무런 체크없이 무조건적으로 프로토타입을 교체할 수 없도록 접근자 프로퍼티를 통해 프로토타입에 접근할 수 있도록 구현되어 있다.

🍪 prototype & constructor 프로퍼티

.prototype

  • 함수 객체만이 갖는다.
  • 생성자 함수가 생성할 인스턴스의 프로토타입을 가르킨다. (쉽게 말하면 생성자 함수가 만들어낼 자식들의 부모를 카르킴)
  • 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않는다.

사용 목적 : 생성자 함수가 자신이 생성할 객체의 프로토타입을 할당하기 위해 사용

.constructor

  • 자신을 생성한 생성자 함수를 가르킨다. (모든 객체는 생성자 함수와 연결되어 있다.)
  • prototype의 프로퍼티
function Person() {
	this.name = name;
}

const me = new Person('movie');

// Person.prototype.constructor === Person
// me.constructor == Person 
// (Person.prototype === me._ _ proto _ _)
  • 생성된 객체는 prototype 프로퍼티가 없음에도 constructor 프로퍼티에 접근할 수 있다. 이유는 자신의 prototype 객체에 constructor 프로퍼티가 존재하기 때문에 상속받아 사용할 수 있기 때문이다.
  • 리터럴 표기법에 생성된 객체는 constructor 프로퍼티가 무조건 자신을 생성한 생성자 함수라고 단정 지을 수 없다. (리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하기 때문에 가상적인 생성자 함수를 갖는다.)
  • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다. (프로토타입은 생성자 함수와 더불어 생성된다.)

🍪 사용자 정의 생성자 함수 vs 빌트인 생성자 함수

생성자 함수와 프로토타입은 객체가 생성되기 이전에 이미 객체화되어 존재한다. 객체를 생성하면 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.

사용자 정의 생성자 함수

  • 생성자 함수 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입이 더불어 생성된다.
  • 생성된 프로토타입의 프로토타입은 언제나 Object.prototype이다.

빌트인 생성자 함수 (Object, String ..)

  • 프로토타입 생성 시점은 빌트인 생성자 함수와 같다.
  • 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다.

🍪 객체의 생성

객체의 생성 방식

  • 객체 리터럴
  • Object 생성자 함수
  • 생성자 함수
  • Object.create 메서드
  • 클래스 (ES6)

여러 객체의 생성 방식이 있고, 각 방식마다 세부적인 객체 생성 방식은 차이가 있지만 추상 연산 (OrdinaryObjectCreate)에 의해 생성된다.

OrdinaryObjectCreate

  • 자신이 생성할 객체의 프로토타입을 인수로 받는다. ([[Prototype]] 내부 슬롯에 할당한다.)
  • 자신이 생성할 객체의 프로퍼티 목록을 옵션으로 전달할 수 있더.
  • 즉, 프로토타입은 OrdinaryObjectCreate에 전달되는 인수에 의해 결정된다. 인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정된다.

🍪 프로토타입 체인

객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다.

  • JS가 객체지향 프로그래밍의 상속과 프로퍼티 검색을 구현하는 메커니즘
  • 프로토타입 체인의 최상위에 위치하는 객체는 Object.prototype이다. 모든 객체는 Object.prototye을 상속받는다.
  • Object.prototype의 프로토타입은 null이다.
  • 스코프 체인과 프로토타입 체인은 서로 연관없이 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.
  • instanceof 에서는 객체가 프로토 타입 체인상에 존재하면 true를 평가하고 아닌 경우는 false로 평가된다.

프로퍼티 섀도잉

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 객체에 추가하면 프로퍼티가 오버라이딩되어 프로토타입의 프로퍼티가 가져진다. 이를 프로퍼티 섀도잉이라고 한다.

  • 만약 해당 객체에서 프로퍼티를 삭제한다면 인스턴스 프로퍼티가 삭제되고 프로토타입 프로퍼티는 그대로 존재하며 다시 접근하게 되면 프로토타입 프로퍼티에 접근하게 된다.
  • 프로토타입의 프로퍼티는 하위 객체를 통해 변경 또는 삭제가 불가능하다. 프로토타입의 프로퍼티를 변경하려면 하위 객체가 아닌 프로토타입에 직접 접근해야 한다.

오버라이딩 : 부모가 가진 메서드를 자식이 재정의하여 사용하는 방식

in / Reflect.has

프로토타입 체인 상에 존재하는 모든 프로토타입에서 프로퍼티를 검색한다

Object.prototype.hasOwnProperty

인스턴스의 고유 프로퍼티일 경우에만 true를 반환한다.

🍪 프로토타입 교체

  • 프로토타입의 임의의 다른 객체로 변경할 수 있다.

  • 객체 간의 상속 관계를 동적으로 변경할 수 있다.

  • 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 번거롭기 때문에 직접 교체하지 않는 것이 좋다.

  • 생성자함수.prototype = 교체할 프로토타입

    • 이를 객체 리터럴로 하면 객체 리터럴에는 constructor 프로퍼티가 존재하지 않아서 constructor를 검색하면 생성자 함수가 아닌 Object가 나온다. (즉, constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.)
  • prototype 프로퍼티에 다른 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로퍼티를 변경하는 것이고 / _ proto _ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.

IMG_7B814512C3CE-1

🍪 직접 상속

Object.create 를 통한 직접 상속

  • 첫번째 매개변수는 생성할 객체의 프로토타입
  • 두번째 매개변수는 생성할 객체의 프로퍼티를 갖는 객체 (옵션)
  • return : 새로운 객체
const parent = { name: '' }

const child = Object.create(parent)

// Object.getPrototyeOf(child) === parent

특징

  • new 연산자 없이도 객체를 생성할 수 있다.
  • 프로토타입을 지정하면서 객체를 생성할 수 있다.
  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.

Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것은 권장되지 않는다. (by ESLint)

왜?

Object.create를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있기 때문

const obj = Object.create(null);
obj는 Object.prototype의 빌트인 메서드를 사용할 수 없다.

에러 발생 위험도를 떨어뜨리기 위해 Object.prototype 빌트인 메서드는 간접적으로 호출하는 것이 좋다.

객체 리터럴을 통한 직접 상속

  • Object.create 를 통한 직접 상속은 두번째 인자로 프로퍼티를 정의해야 하는 부분이 번거롭다.
  • ES6에서는 객체 리터럴 내부에서 _ proto _ 접근자 프로퍼티를 사용해서 직접 상속을 구현할 수 있다.
const parent = { x: 10 };

const child = {
	y: 20,
	_ _ proto _ _: parent
}

🍪 정적 프로퍼티(메서드도 함께)

  • 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티
  • 생성자 함수는 객체이므로 자신의 프로퍼티를 소유할 수 있다. 생성자 함수가 소유한 프로퍼티를 정적 프로퍼티라고 한다. 이는 생성자 함수로 생성한 인스턴스로 참조/호출할 수 없다. (프로토타입 체인에 속한 객체의 프로퍼티가 아니기 때문에)
  • 예시는 Object.create
  • 메서드 내에서 this를 사용하지 않는다면 그 메서드는 정적 메서드로 변경할 수 있다.

IMG_8ACEACF3F16D-1

🍪 프로퍼티 열거

  • 객체의 프로퍼티를 순회하고 열거하는 데에는 for..in이 사용된다. in 이 프로토타입의 프로퍼티까지 열거하는 반면에 for..in 을 사용했을 때는 프로토타입의 프로퍼티는 열거되지 않는 경우가 있다.
    • 이는 해당 프로퍼티 (프로토타입의 프로퍼티 중 하나가)가 열거할 수 없도록 정의되어 있는 프로퍼티이기 때문이다.
    • 즉, [[Enumerable]] 값이 false이다.
    • 디스크립터 객체 : 프로퍼티 어트리뷰트 정보를 담고 있는 객체
    • for..in은 [[Enumerable]] 값이 true인 프로퍼티를 순회하며 열거한다.
  • 배열도 객체이므로 프로퍼티를 가질 수 있고 for in을 사용할 경우 프로퍼티도 출력된다. 반면 forEach는 요소가 아닌 프로퍼티는 제외한다.
  • for..in은 객체의 프로토타입의 프로퍼티도 순회하므로 Object.prototype.hasOwnProperty로 확인하는 추가 작업이 필요하다. 이를 생략하기 위해서는 for..in보다는 Object.keys/values/entries 메서드를 사용하는 것이 권장

참고

  • 모던 자바스크립트 Deep Dive
profile
영화보관소는 영화관 😎

2개의 댓글

comment-user-thumbnail
2022년 9월 19일

누구는 뽀개고.. 누구는 빠개고.. 프로토타입이 남아나질 않겠네요.. 🤔

1개의 답글