[ js ] 프로토타입

꼬꼬마·2022년 1월 21일

그룹스터디 🦆 팀 러버덕과 함께!

<목차>
1. 객체 생성자 함수의 문제점
2. 프로토타입이란?
3. prototype프로퍼티
4. _ proto _프로퍼티
5. 프로토타입 체인
6. 정리

1.객체 생성자 함수의 문제점

생성자 함수로 간편하게 동일한 모양의 객체를 수십개 만들 수 있지만
생성자 함수 안에 메서드가 있다면,
메서드는 힙 메모리에 할당되는데 Person인스턴스가 수십개 만들어질때마다 힙메모리에 Person의 sayHi메서드가 할당되므로 효율적이지못하고 메모리를 소비하게 된다.그래서 프로토타입를 사용한다.

프로토타입 사용 전)

function Person(name){
  this.name=name;
  this.sayHi= function(){
      console.log(`Hi ${this.name}`);
 }
 const mike= new Person('mike');
 const sally = new Person('sally');

2.프로토타입이란?

js에서 함수는 객체이므로 함수도 프로퍼티를 가지는데
함수는 프로퍼티로 arguments,caller,prototype,[[prototype]]등 가지고 있다.

프로토타입은 상속의 개념이다.
java나 다른 언어에서는 클래스를 상속하지만 js에서는 객체를 상속한다.
상속이란 부모역할을 자식이 이어받는다는 의미로 즉,js에서는 특정 객체가 다른 객체의 기능을 그대로 받아온다는 뜻이다.
생성자의 함수 문제점을 바로 js식의 상속, 즉,프로토타입을 이용해서 메모리낭비 문제를 해결 할 수 있다.
프로토타입 적용 전 )
우리는 매번 인스턴스를 만들때마다 동일한 메서드가 생성된다.-> 메모리 낭비 + 효율👎
프로토타입 적용 후 )
sayHi메서드를 생성자 함수 내에서 직접 정의하는것이아닌, 프로토타입으로 정의한다.

function Person(name){
  this.name=name;
 👉  Person.prototype.sayHi= function(){
       console.log(`Hi ${this.name}`);
    }
 }
 const sally = new Person('sally');
 const mike = new Person('mike');

이제 sayHi메서드를 생성자 함수안에 정의하는것이아니라 생성자 함수의 prototype 프로퍼티가 가리키는 prototype객체 에 등록한다.
그럼, 새로 생성한 인스턴스마다 sayHi 메서드를 가지고 있지않지만 prototype객체에 있는 sayHi메서드를 사용할 수 있다.
(인스턴스안에있는 [[Prototype]]이 prototype객체를 상속하는데 이건 나중에 다루고 전반적인 그림만 보면 된다.)

sally.sayHi(); // Hi sally;


인스턴스는 메모리에 sayHi메서드가 없지만 [[Prototype]]을 통해서 생성자함수의 프로토타입 객체를 참조할수있다.
즉,인스턴스는 프로토타입 객체에있는 모든 메서들과 프로퍼티들을 상속받는다.
따라서 sayHi메서드를 인스턴스도 사용할 수 있는 이유다.
생성자함수안에 프로토타입을 적용한 후 다시 보면,

사진에서보듯이, prototype객체에는 기존에 있던 constructor말고 sayHi가 새로 추가된 것을 볼 수 있다. (constructor,prototype,[[Prototype]]은 지금부터 알아보자)

3.prototype프로퍼티

아까에서 봤듯이, 생성자 함수안에 prototype프로퍼티가 들어있는 것을 볼 수 있다.
모든 함수에는 prototype프로퍼티가 들어가있다.
prototype프로퍼티는 프로토타입 객체를 가리킨다.

프로토타입객체는 기본적으로 constructor[[Prototype]]을 갖는다.

constructor프로퍼티는 자신을 참조하고 있는 생성자 함수를 가리킨다.
즉, 생성자함수의 prototype프러퍼티와 프로토타입객체의 constructor는 서로를 참조한다.

그리고 인스턴스는 prototype객체를 상속받는다.

하지만, 모든 함수객체가 prototype프로퍼티를 가지는 것은 아니다.
non-constructor(화살표함수,메서드)함수는 prototype프로퍼티를 가지고 있지 않다.
constructor 를 가지는 함수만 prototype프로퍼티를 가지고 있다.

4. __proto__

__proto__ 는 사실 [[Prototype]]에 접근하기위한 접근자 프로퍼티이다.

접근자 프로퍼티란 외부에서 직접 함부로 데이터를 조작하지 못하게 getter,setter으로 안전하게 값을 읽고 쓸 수 있게해주는 방식이다.

모든 객체[[Prototype]] 내부 프로퍼티를 갖는다.
[[Prototype]]내부 슬롯(자바스크립트 엔진의 내부로직)이며, 자신에게 상속해주는 부모인 프로토타입객체를 가리킨다.
따라서 객체는 [[Prototype]]이 가리키는 프로토타입 객체의 모든 프로퍼티와 메서드를 사용 할 수 있다.
내부슬롯이므로 EC5까지는 [[Prototype]]에 접근할 수 없었다.
EC6부터는 __proto__프로퍼티를 통해 간접적으로 [[Prototype]]에 접근할 수 있다.
따라서 [[Prototype]]__proto__로 표현한다.

function Person(name){
       this.name=name;
       Person.prototype.sayHi= function(){
           console.log(`Hi ${this.name}`);
       }
  }   
const sally= new Person('mike');


인스턴스의 [[Prototype]] 또는 __proto__는 Person생성자 함수의 프로토타입객체를 가리킨다.
즉, 프로토타입 객체안에 있는 모든 프로퍼티와 메서드를 상속받으므로,
constructor를 상속받기때문에 인스턴스가 Person생성자의 모든 프로퍼티와 메서드를 참조할 수있다.

console.log(Person.prototype=== sally.__proto__); //true
console.log(Person === sally.constructor); // true


사진을보면 인스턴스는 __proto__ ( [[Prototype]] )을 통해 그대로 생성자 프로토타입객체를 물려받은것을 볼 수 있다.
인스턴스는 단순 객체여서 constructor가 존재하지 않지만 상속을 통해 물려받은것을 알 수 있다.
생성자함수는 가지고있는 prototype프로퍼티는 가지고있지 않는것도 확인 할 수 있다.
그리고 여기서, [[Prototype]]안에 또 [[Prototype]]:Object 가 있는 것을 볼 수 있는데 이건 프로토타입체인에서 다룰것이다.

정리하자면, prototype프로퍼티는 constructor를 가지는 함수에서만 가지고 있고 일반 객체리터럴이나({..}) non-constructor함수는 prototype프로퍼티를 가지고 있지 않다.
반면,[[Prototype]]프로퍼티는 모든 객체가 가지고 있고 내부슬롯이므로 직접 접근하지 못하고 __proto__ 접근자 프로퍼티를 이용해서 간접 접근할 수 있다. 또한 __proto__([[Prototype]])는 자신에게 상속해주는 프로토타입 객체를 가리키고있어서 __proto__가 가리키는 프로토타입 객체의 프로터피들을 사용할 수 있다.
이처럼, 객체는 자신이 가지고 있지 않는 프로퍼티나 메서드를 상속해주는 프로토타입 객체에 접근하여 사용할 수 있다.

5.프로토타입 체인

[[Prototype]]은 모든 객체가 가지고 있으므로 모든 객체에는 [[Prototye]]이 있다고 아까 말하였다.
__proto__가 가리키는 객체안에있는 __proto__가 가리키는 객체안에있는 __proto__가 가리키는 객체 ..........
이렇게 __proto__로 계속해서 부모의 부모로 거슬로 올라가는 연결고리프로토타입 체인이라고한다. 이렇게 계속 올라가다보면 최종 목적지는 null을 프로토타입으로 가지는 Object에서 끝난다.
프로토타입프로퍼티 설명할때 나와있었지만 프로토타입프로퍼티에는 constructor프로퍼티와 [[Prototype]]을 가지고 있다고 하였다.
그림상으로 표현할때 [[Prototype]]은 생략하고 construcotr만 표현했는데 사실은 [[Prototype]]은 Object의 프로토타입객체를 가리킨다.
Object.prototype 모든 객체들의 원본이 되어주는 프로토타입이다.

최종 보스인 Object의 __proto__는 null을 가리키며 끝나게 된다.

6.정리

프로토타입 정말 복잡하고 어렵다... 깊게 들어가면 정말 복잡하고 너무 복잡하다.. 아마 여기까지 읽을 사람은 없을거같다... 있다면 틀린정보가 있어서 뭔소리하나 하는 사람...?일단 정리하자면, js는 자바언어처럼 class개념이 없다. 자바에서는 class를 상속하고 오버라이딩하지만 js는 프로토타입 체인을 사용해서 객체의 정보들을 다른 객체로 전달한다. 즉, 프로토타입을 상속하는 객체 지향 언어를 프로토타입기반 객체 지향 언어라고 한다.
맨 처음에 나왔듯이, 프로토타입으로 정의하면 인스턴스마다 메모리 낭비를 하지 않아도 되고 효율적으로 프로그래밍을 할 수 있다.
하지만 여기서 다뤘지 않았지만 프로토타입을 수정,변경할 수 있다. 개발자가 직접 상속할 객체를 정할 수 있다.즉, 상속 관계를 마음대로 변경할 수 있다는 의미로 안전하지 않고 번거롭고 비효율적이다.
따라서 프로토타입을 사용하는 것은 지양하고 ES6부터 지원되는 class를 지향하는 것이 좋다.(여기서 js의 class는 자바의 class와 다르고 js에서는 class도 함수로 단지, 프로토타입을 좀 더 간편하게 사용하기 위함이다.)

profile
동글한지구🌍

0개의 댓글