프론트엔드 개발/JavaScript

자바스크립트) 프로토타입

Ella Seon 2023. 6. 19. 17:53

0.프로토타입

프로토타입은 객체가 다른 객체로부터 속성과 메서드를 상속받기 위한  템플릿 역할을 한다. 

자바스크립트에서 모든 객체는 프로토타입을 가지며, 이를 통해 다른 객체의 속성과 메서드를 공유할 수 있다.

 

🔸프로토타입 객체
자바스크립트에서 어떤 객체는 반드시 부모 객체를 가지며, 이 부모객체를 프로토타입이라고 한다. 즉, 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)을 제공한다. 프로토타입을 상속받은 하위(자식) 객체는 상위(자식) 객체의 모든 속성(프로퍼티)를 사용할 수 있다. 

🔸[[Prototype]] : 프로토타입 링크
프로토타입 객체를 가리키는 내부슬롯으로 사용자가 직접 접근 X, __proto__접근자를 통해 간접적으로 접근 가능

🔸__proto__ : 접근자 프로퍼티
프로토타입에 접근하기 위한 접근자 프로퍼티 [[Prototype]] 내부 슬롯에 간접적으로 접근하게 함

 

 

🔸프로토타입 만드는 방법

1) 생성자 함수를 정의한다.

아래 예제에서 Person 은 부모객체 이고 학생1은 자식객체이다.

2) 생성자 함수의 prototype 객체에 값을 추가하면 모든 자식들이 물려받기가 가능하다. 

+) 값을 여러개 부여할 수도 있고, 함수도 집어넣을 수 있다. 

+) prototype에 추가된 데이터들은 자식들이 직접 가지는게 아니라 부모만 가지고 있다. 

학생1에 gender를 부여한것도 아니고 prototype에 값을 추가한건데 어떻게 학생1에 gender가 들어있는거지?

먼저 학생1이 직접 gender를 가지고 있는가를 살펴본다. 

위 예제에서는 학생1이 gender을 가지고 있지 않다. gender가 없다면
학생1의 부모 유전자가 gender를 가지고 있는가? 부모유전자(Person.prototype)을 검사해본다. 있으면 출력하는 원리로 돌아간다. 


🔸프로토타입 체인

- 객체에서 프로퍼티를 읽거나 메서드를 호출하려는데 해당 프로퍼티가 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티나 메서드를 찾는다. 만약 해당 프로토타입에서도 접근하려는 프로퍼티나 메서드가 없으면 찾을때까지 프로토타입 체인을 거슬러 올라가 해당 프로퍼티나 메서드를 찾는다.  이런 동작 방식을 자바스크립트에서 프로토타입 체인이라고 부른다.

 

ex)

foo.a 는 1을 foo.b는 2를 반환한다. foo 객체에 정의되지 않은 foo.c를 호출하게되면 어떤 값을 반환할까? undefined를 반환할까?

 

foo 객체의 프로토타입 체인을 보기 전까지는 알 수 없다. 

foo객체의 속성에 접근하게 되면 프로토타입 체인은 호출한 foo객체의 속성부터 Object.prototype까지까지 프로토타입 링크를 따라 차례차례 탐색하기 시작한다. 위 그림에서는 '원형 객체2'에 c가 정의되어 있기 때문에 foo.c는 undefined가 아닌 7을 반환한다.
만약, 체인 상의 어떤 객체에도 존재하지 않는 foo.d에 접근하게 되면, 어떤 값을 반환할까? 프로토타입 체인의 최종 원형인 Object.prototype까지 탐색한 후 값이 없음을 확인하고 undefined를 반환한다.
자바스크립트에서는 객체 속성에 접근하게 되면 해당 객체의 속성들만 탐색한 후 결과를 반환하는 것이 아니라, 최종 원형인 Object.prototype까지 탐색한 후 결과를 반환한다는 것을 기억하자.

 

🔸모든 객체의 최상위 객체는 Object.prototype

모든 객체의 최상위 객체는 Object.prototype 이며, 이 객체의 부모 객체는 null 이다. 프로토타입 체이닝의 최종 종착지는 null이다. 

[[Prototype]] 링크  : 자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있다.  이는 자바스크립트 엔진의 내부 속성으로 사용자가 직접적으로 접근할 수 없고, 오직 __proto__접근자로만 간접적으로 접근할 수 있다. (obj.[[Prototype]]으로 이 속성에 직접 접근할 수 없고, obj.__proto__ 로 접근자로만 간접적으로 접근할 수 있다. ) [[prototype]] 은 자신의 프로토타입 객체를 참조하고 있다. 즉, 프로토타입 객체에 대한 참조값은 [[prototype]] 내부 슬롯에 저장되어 있고, 이를 __proto__접근자로 접근할 수 있는 것이다. 

 

🔸__proto__ 접근자 프로퍼티를 통해 프로토타입 링크에 접근하는 이유는?

__proto__를 굳이 사용하여 [[prototype]]의 값에 접근하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서다. 아래 코드 처럼 두 객체가 서로의 프로토타입으로 설정하는 것이 에러 없이 정상적으로 작동한다면 비정상적인 프로토타입 체인이 만들어지기 때문에, __proto__는 에러를 발생시킨다.

프로토타입 체인은 무조건 단방향 리스트로 구현되야 한다. 즉, 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야 한다. 만약 서로 참조하는 순환형 프로토타입 체인이 만들어지게 된다면 프로토타입 체인의 종점이 존재하지 않기 때문에 프로퍼티를 검색할 때 무한루프에 빠지게 된다. 따라서 아무런 체크 없이 무조건적으로 프로토타입을 교체할 수 없도록 __proto__를 이용해 프로토타입을 교체하도록 구현했다.


🔸자바스크립트의 내장함수 동작은 어떻게 되는가?

내가 만든 array에 arr.toString()을 어떻게 붙일 수 있을까?

내가 만든 array의 부모 유전자가 toString()을 가지고 있기 때문이다. 

var arr = [1,2,3];
console.log( arr.toString() ) // ''1,2,3''

1) arr가 toString()을 가지고 있는가? array 자체에는 toString이 없다. 
2) 그럼 부모 prototype에 있는가? 응. Array.prototype에는 toString() 이 있다. 

3) 만약 부모의 prototype에 없었을 경우 부모의 부모 prototype도 살펴본다. 

 

+) arr 의 부모는 Array이다. 

var arr = [1,2,3];
var arr = new Array(1,2,3);

new 키워드로 Array 를 만드는 방법도 있다. 

new Array는 결국 Array 로부터 자식을 하나 새로 뽑아달라는 뜻이다. 

그래서 Array(부모)로 부터 생성된 자식들은 Array의 prototype에 부여되어있는 함수, 데이터들을 자유롭게 사용할 수 있다.

 

콘솔창에 Array.prototype이라고 출력해보면 우리가 자주쓰는 sort,map 이런것들이 나온다.

그래서 Array의 자식들은 전부 이런 함수들을 쉽게 가져다 쓸 수 있었다. 


1. prototype의 특징

1️⃣ prototype은 함수에만 생성된다.

arr라는 변수에 prototype을 했을 때 undefined라고 나온다. 

2️⃣ 하지만, 부모의 prototype을 찾고 싶다면 __proto__를 해주면된다.

결국 아래 예제처럼

학생1.__proto__  === Person.prototype이 되는것이다. 


2. Prototype 조작을 지양하자

1️⃣ 이미 JS 는 많이 발전했다.

- prototype이 아닌 class 로 상속기능을 구현할 수 있다. 

- 직접 만들어서 모듈화할 수 있다. or 직접 만들어서 모듈화 -> NPM 에 배포 

 

2️⃣ JS 빌트인 객체(기본 내장된 객체)를 건들지 말자

- 빌트인 객체의 'prototype'을 수정하면 해당 객체를 사용하는 모든 코드에 영향을 미칠 수 있다. 

- 다른 개발자들은 이러한 변경에 대해 예상하지 못하고 있을 수 있고, 코드의 동작이 예상치 않은 방식으로 변경될 수 있다. 


참고자료

프로토타입은 코딩애플이 진리이다.

https://www.youtube.com/watch?v=wUgmzvExL_E 

유데미 [클린코드 자바스크립트] Prototype 조작 지양하기