Prototype 프로토타입

이진혁·2022년 9월 23일
0
post-thumbnail

오늘도 열심히 코딩을 하다가 문득 든 생각이 있다

let arr = [1,2,3]

arr.toString() // '1,2,3'

위의 코드와 같이 우리는 대체 어떻게 내장함수를 쓸 수 있는 걸까?

코드를 뜯어서 해석해보면 arr 안에 있는 toString()이라는 함수를 실행한다.

라고 해석할 수 있는데 arr을 출력해보면 배열만 심플하게 나오지 toString()이라는 함수는 존재하지 않는다,,,!

그럼 대체 우리는 왜 내장함수를 사용할 수 있는 걸까?

열심히 구글링하고 삽질해 본 결과 우리는 Prototype 이라는 개념 덕분에 toString() 같은 내장 함수를 사용할 수 있었던 것이다

이렇게 덤덤하게 적고 있지만 사실 작동원리에 대해서 알고나니 너무 충격적이었고 깊은 깨달음(?),, 감동(?),,을 받고 잊지 않기 위해 이 글을 작성한다.

그럼 지금부터 우리가 당연시 썼었던 내장함수들이 어떤 원리로 작동하는지 더불어 Prototype의 개념까지 확실하게 짚고 넘어가보자!

상속 feat.constructor

아래의 코드를 살펴보자

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}
let 학생1 = new 기계();
let 학생2 = new 기계();

위의 코드를 해석해보면 기계라는 constructor가 가진 name, age 속성들을 그대로 물려받아서 오브젝트들을 뽑아주는 코드로 해석할 수 있다.

이런 걸 멋진 객체지향 용어로 상속(inheritance)이라고 하는데 뭐 재산 물려주는 상속과 비슷하다고 해서 상속이라고 부른다고 한다

그래서 물려주고 상속해주는 것은 부모 물려받고 상속받는 오브젝트들은 자식이라고 많이 비유해서 부른다

근데 자바스크립트엔 constructor 말고도 상속기능을 구현할 수 있는 장치가 하나 더 있다!

바로 prototype 이라는 것인데 알아보도록 하자

Prototype

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}
let 학생1 = new 기계();
let 학생2 = new 기계();

console.log(기계.prototype);

위의 코드와 같이 constructor 즉 생성자 함수(기계)를 만들게 되면 prototype이라는 항목이 기계 안에 몰래 생성된다

궁금하니 바로 콘솔에 찍어보자

실제로 찍어보니 constructor 밑에 앙큼하게 prototype이 몰래 들어온 것을 확인할 수 있다.

그렇다면 갑자기 생겨난 prototype이라는 비밀 공간은 왜 존재하고 어디에 쓰는 거냐면

이것이 바로 부모의 유전자역할을 해주는 일종의 비밀 공간이라고 보면 된다

우리는 왜 키가 작고 못생겨서 컴퓨터 앞에서 개발이나 하고 있는 걸까?

사실 키가 작고 못생긴 건 우리 탓이 아니라 부모님 탓이다

부모님이 작은 키가 들어있는 유전자를 물려줬기 때문에 키가 작은 거다,,!

마찬가지로 prototype은 자식들이 물려받을 수 있는 유전자라고 생각하면 된다.

기계.prototype은 기계의 유전자이다.

기계.prototype에 뭔가 변수나 함수가 들어가있다면

기계로부터 생성되는 새로운 오브젝트들(자식들)은 전부 그걸 그대로 물려받아 쓸 수 있다는 뜻이다.

아래의 코드를 살펴보자

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}

기계.prototype.gender = '남';

let 학생1 = new 기계();
let 학생2 = new 기계();

console.log(학생1.gender); //'남'이 출력

기계의 prototype이라는 곳에 { gender : '남' } 이라는 key/value 한쌍을 저장해 보았다

기계의 prototype, 즉 유전자에 gender : '남'이라는 데이터를 추가한 것이다.

이제 학생1, 학생2 같은 기계로부터 생성되는 모든 자식들은 gender라는 속성을 사용할 수 있게 된다.

한번 출력해보자

wow

결론은 prototype 이라는 비밀 공간을 이용하면 똑같이 상속기능을 만들 수 있다는 뜻이다

그렇다면 도대체 왜 prototype에 추가한 데이터는 자식들이 자유롭게 가져다 쓸 수 있을까?!

원리

자바스크립트는 오브젝트에서 데이터를 뽑을 때 확인하는 순서가 있다.

아래의 코드를 살펴보자

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}
기계.prototype.gender = '남';
let 학생1 = new 기계();

console.log(학생1.gender)

학생1.gender라고 사용하면 '남'이 출력된다.

그 이유는 자바스크립트는 오브젝트에서 값을 출력할 때 이런 순서로 물어본다.

  1. 학생1에 직접 gender라는 값이 있는가?
  2. 그럼 부모 유전자에 gender라는 값이 있는가?
  3. 그럼 부모의 부모 유전자에 gender라는 값이 있는가?
  4. 그럼 부모의 부모의 부모의 유전자에 .. 그게 있는가?

자바스크립트는 이런 알고리즘으로 작동한다.

그냥 쉽게말하자면 오브젝트에서 값을 뽑을 때 

  1. 내가 직접 가지고 있는지 검사
  2. 내가 가지고 있지 않으면 부모 유전자들을 차례로 검사

이런식으로 검사한다고 알아두면 된다.

그래서 학생1이라는 오브젝트가 gender라는 값을 가지고 있지 않지만

부모의 유전자(기계.prototype) 에 있는 gender라는 걸 출력할 수 있는 이유이다!

내장함수의 작동원리

그렇다면 자바스크립트 내장함수를 쓸 수 있는 이유는 뭘까?

바로 앞서 말했던 prototype과 아주 밀접한 관련이 있다

아래의 코드를 살펴보자

let arr = [1,2,3];

console.log( arr.toString() ); //가능

내가 만든 array에 arr.toString() 이렇게 붙일 수 있는 이유는 바로

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

하지만 내가 만든 array는 부모 기계로 부터 뽑은게 아닌데 뭔소리냐고 물어볼 수 있다.

실은 우리가 array나 object 자료형 만들 때 부모가 있긴 있다!

아래의 코드를 살펴보자

let arr = [1,2,3];

let arr = new Array(1,2,3);

위 코드 두줄은 같은 완전히 똑같은 의미이다.

하지만 위는 사람이 array 만드는 방식, 밑은 컴퓨터가 array 만드는 방식으로 볼 수 있다

사람은 귀찮아서 [] 그냥 대괄호쳐서 만드는데 내부적으로는 저렇게 new 키워드를 항상 이용해서 array/object를 만들어준다

그럼 new Array() 이게 무슨 뜻일까?

Array라는 기계로부터 자식을 하나 새로 뽑아주세요 라는 뜻 아닐까?

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

궁금한 건 못 참으니 Array라는 기계의 유전자가 진짜 있는지 바로 확인해보자

놀랍게도 우리가 평소에 쓰던 sort, map, push, forEach 이런 것들이 등장한다.

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

또한 Object 자료형도 똑같이 new Object() 이런 식으로 만들어주기 때문에 부모의 prototype에 있던 함수들을 자유롭게 사용가능했던 것이다..(충격)

prototype의 특징

prototype은 constructor 함수에만 몰래 생성

우리가 만드는 일반 object, array 이런거 만들어도 거기엔 prototype이 없다

끝이다,,!

일반 object 같은걸 상속하고 싶으면 constructor 함수를 만들던가..

Object.create()를 쓰거나 class를 쓰거나 셋 중 하나 하면 된다!

부모 유전자를 찾고 싶다면 proto를 출력해보면 된다

부모로부터 생성된 자식 object들은 __proto__ 라는 속성이 있다.

이걸 출력해보면 부모의 prototype이 출력된다.

그래서 __proto__는 부모의 prototype과 같은 의미이다.

한 번 출력해보자

각각 출력해보면 똑같은 게 나온다 때문에 __proto__는 부모 prototype을 의미 한다고 알아두면 된다

그냥 proto는 내 부모 유전자가 뭔지 유전자 검사하고 싶을 때 쓸 수 있는 값

proto를 직접 등록하면 object끼리 상속기능을 구현가능

proto는 부모의 prototype을 의미한다 라고 했다

그럼 어떤 object에다가 proto를 강제로 하나 설정해버리면 어떻게 될까?

바로 부모가 생겨버린다

한번 만들어보자

let 부모 = { name : 'Kim' };
let 자식 = {};

자식.__proto__ = 부모;
console.log(자식.name);

지금 부모와 자식 object를 하나씩 만들었다.

그리고 셋째줄에서 자식의 proto에 부모를 집어넣었다.

그럼 자식의 부모 유전자는 { name : 'Kim' } 이라는 오브젝트가 되는 것이다.

그렇게 되면 자식은 이제 자식.name 속성을 자유롭게 사용할 수 있다.

이렇게도 상속기능을 구현할 수 있다!

느낀점

그동안 너무나 당연하게 내장함수들을 써왔었다

조금만 생각해보면 이상한 점을 느낄 수 있었을텐데 너무 생각없이 코딩만 한 것 같다,,

이번 기회에 든 위화감(?)이 너무나 다행이었고 이제라도 개념을 정확히 짚고 작동원리에 대해

알게 되니 너무나 뿌듯,충격,감동 등등,,(?) 여러 감정을 느꼈다.

앞으로도 너무 당연하게 생각하지 말고 항상 왜? 라는 질문을 던지는 습관을 길러야겠다!

profile
개발 === 99%의 노력과 1%의 기도

0개의 댓글