JavaScript 프로토타입

seul_velog·2021년 12월 10일
0

JavaScript

목록 보기
15/25
post-thumbnail

상속(inheritance)

상속이란 새로운 클래스에서 기존 클래스의 모든 프로퍼티와 메소드를 사용할 수 있는 것을 의미한다.

상속을 통해 새로운 프로그램의 요구에 맞게 기존 클래스를 수정하여 재사용할 수 있으며, 클래스 간의 종속 관계를 형성함으로써 객체의 관계를 조직화할 수 있는 장점이 있다.

따라서 이러한 상속은 추상화, 캡슐화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나가 된다.




클래스기반 VS 프로토타입 기반의 객체 지향 언어

📌 Class-based

Java, C++, C#, Python, PHP, Ruby, Object-C와 같은 클래스 기반의 객체 지향 언어는 객체 생성 이전에 클래스를 정의하고 이를 통해 객체(인스턴스)를 생성한다.

📌 prototype-based

자바스크립트는 프로토타입 기반의 객체 지향 언어이다.
JavaScript는 class라는 개념이 없기 때문에 객체의 원형인 프로토타입을 이용한 클로닝(Cloning: 복사)과 객체특성을 확장해 나가는 방식을 통해 새로운 객체를 생성해 낸다. (프로토타입을 기반으로 상속을 흉내내어 구현해 사용한다고 한다. 🤔 )

ECMAScript 6에서 클래스가 추가되었다.

ES5에서는 생성자 함수와 프로토타입, 클로저를 사용하여 객체 지향 프로그래밍을 구현하였다.

ES6의 클래스는, 클래스 기반 언어에 익숙한 프로그래머가 보다 빠르게 학습할 수 있는 새로운 문법을 제시하고 있다. 하지만, ES6의 클래스 문법의 추가로 인해서 기존의 프로토타입 기반을 폐지하고 새롭게 클래스 기반으로 바뀌었다는 의미는 아니다. 사실 클래스도 함수인데, 클래스와 생성자 함수가 정확히 동일하게 동작하지는 않고 클래스가 보다 엄격하다.

  • ES6 클래스는 class 키워드를 사용하여 정의한다.

📌 객체지향 프로그래밍의 기본적 원리

  • 연관되어 있는 것들을 grouping, Categorizing한다.
  • 연관되어 있는 변수와 연관되어 있는 메소드를 하나의 객체 안에 넣고, 연관되어 있지 않은 데이터는 별도의 객체에 다시 담아서 서로 분리한다.
  • 즉, 좋은 부품의 로직을 만드는 것이다.




객체와 생성자 함수

객체를 생성하는 함수이다. 기존 함수와 동일한 방법으로 생성자 함수를 선언하고 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

  • 생성자 함수 : 생성자 함수는 함수이다.
  • 함수 : 함수는 javascript에서 객체이다.
  • 객체 : key와 value를 가질 수 있다.

📌 생성자 함수 또한 객체이기 때문에 key와 value를 가질 수 있다.

📌 객체는 프로퍼티를 가질 수 있는데 prototype이라는 프로퍼티는 그 용도가 약속되어 있는 특수한 프로퍼티다.

📌 객체는 언제나 함수(Function)로 생성된다. ▼

function Person() {} // => 함수
var personObject = new Person(); // => 함수로 객체를 생성

personObject 객체는 Person 이라는 함수로 생성된 객체이며, 일반적인 객체 생성도 마찬가지로, 언제나 객체는 함수에서 시작된다.






📍 Prototype(프로토타입) 객체

  • 자바스크립트의 모든 객체는 프로토타입(prototype)이라는 객체를 가지고 있다. 모든 객체는 그들의 프로토타입으로부터 프로퍼티와 메소드를 상속받는다.

  • 이처럼 자바스크립트의 모든 객체는 최소한 하나 이상의 다른 객체로부터 상속을 받으며, 이때 상속되는 정보를 제공하는 객체를 프로토타입(prototype)이라고 한다.

  • 즉, JavaScript의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있고, 이것은 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 하며, 이러한 부모 객체를 Prototype라고 하는 것이다.

  • Prototype 객체는 생성자 함수에 의해 생성된 각각의 객체에 공유 프로퍼티를 제공하기 위해 사용한다.


❓ 그렇다면 프로토 타입은 언제 사용하게 되는걸까? 🤔

프로토타입은 함수(Function)와 new 키워드를 통해 쓰일 수 있다.

📌 간단한 예제를 통해서 알아보자!

function Person(){
  this.hand = 2;
  this.face = 1;
  this.body = 1;
}

const kim = new Person();
const lee = new Person();

console.log(kim.hand);  // 2
console.log(kim.face);  // 1
console.log(kim.body);  // 1

console.log(lee.hand);  // 2
console.log(lee.face);  // 1
console.log(lee.body);  // 1

✍️
Person 이라는 함수를 통해서 (kim, lee) 객체를 생성했다. 여기서 이들은 hand, face, body 이 세가지를 공통적으로 가지고 있고 데이터 또한 동일하다.
하지만 이것을 메모리의 관점에서 보게 된다면 메모리에는 hand, face, body가 2개씩, 총 6개가 할당된다. 만일 객체를 100개 만들면 300개의 변수가 메모리에 할당되는 일이 벌어지는 것이다. 바로 이럴 때 프로토타입으로 문제를 해결할 수 있다고 한다! 😀

function Person(){}

Person.prototype.hand = 2;
Person.prototype.face = 1;
Person.prototype.body = 1;

const kim = new Person();
const lee = new Person();

console.log(kim.hand);  // 2
console.log(lee.body);  // 1

✍️
Person.prototype 이라는 빈 Object 가 어딘가에 존재하고, Person 함수로부터 생성된 객체(kim, lee)는 이 어딘가에 존재하는 Object 에 들어있는 값을 모두 갖다쓸 수 있는 것이다. 즉 hand, face, body를 객체(kim, lee)가 공유해서 사용하는 것이다.


프로토타입 생성 & 프로토타입에 프로퍼티 및 메소드 추가해보기

📌 프로토 타입 생성하기


function Cat(color, name, age){
  this.color = color;
  this.name = name;
  this.age = age;
}

const myCat = new Cat('하얀색', '꼬비', '5살');

console.log(myCat.color);  // 하얀색 
console.log(myCat.name);  // 꼬비
console.log(myCat.age);  // 5살
console.log(`우리집 고양이 이름은 ${myCat.name} !`);
// "우리집 고양이 이름은 꼬비 !"

✍️ 프로토 타입을 생성하는 가장 기본적인 방법은 객체 생성자 함수를 작성하는 것이다. 생성자 함수를 작성하고 new 키워드를 사용해 객체를 생성하면, 같은 프로토타입을 가지는 객체를 생성할 수 있게된다.


📌 객체에 프로퍼티 및 메소드 추가하기

function Cat(color, name, age){
  this.color = color;
  this.name = name;
  this.age = age;
}

const myCat = new Cat('하얀색', '꼬비', '5살');


myCat.breed = '먼치킨';
myCat.ggobi = function(){
	return this.name + "는 " + this.color 
 	+ " " + this.breed + " " + this.age;
}

console.log( `${myCat.ggobi()} 야옹이 !`);
// "꼬비는 하얀색 먼치킨 5살 야옹이 !"

✍️ 이미 생성된 객체에 새로운 프로퍼티와 메소드를 추가해보자. 여기서 주의할 것은, 새롭게 추가된 breed 프로퍼티와 ggobi() 메소드는 오직 myCat 인스턴스에만 추가되기 때문에 이미 생성된 다른 Cat객체나 이후에 생성되는 Cat객체에 추가되지는 않는다.


📌 프로토타입에 프로퍼티 및 메소드 추가하기

function Cat(color, name, age){
  this.color = color;
  this.name = name;
  this.age = age;
  this.breed = '먼치킨'; 
}

const myCat = new Cat('하얀색', '꼬비', '5살');
const hisCat = new Cat('하얀색', '쵸비', '3살');
console.log(`우리집 고양이는 ${myCat.breed + ' ' + myCat.name}, 그의집 고양이는 ${hisCat.breed + ' ' + hisCat.name } 입니다. `);

✍️ 프로토타입에 새로운 프로퍼티나 메소드를 추가하는 것은 위에서 처럼 객체에 추가하는 법과는 조금 다르다. 생성자 함수에 직접 추가해야만 이후 생성된 다른 객체에도 적용이 가능하기 때문이다.
여기서 5행 을 보면, 프로토타입에 프로퍼티를 추가할 때에는 기본값을 가지게 할 수 있다.

❓ 작성하면서 this.breed = breed; 로 적고 인자(파라미터)는 breed를 추가하지 않았더니 에러가 떴다. 그래서 기본값을 적었더니 문제없이 실행되었다. 동일한 데이터라면 기본값을 적어두고 (위의 첫번째 예제처럼), 각각 다른 데이터가 필요하다면 인자로 받을 자리에 breed를 추가하는 식으로 코딩하면 될지 고민해보았다. 🤔


📌 prototype 프로퍼티

function Cat(color, name, age){
  this.color = color; 
  this.name = name;
  this.age = age;
}

// 현재 존재하고 있는 Cat 프로토타입에 프로퍼티 추가
Cat.prototype.breed = '먼치킨';  
// 현재 존재하고 있는 Cat 프로토타입에 메소드 추가
Cat.prototype.familyCat = function(){
  return this.color + " " + this.breed; 
}  

const myCat = new Cat('하얀색', '꼬비', '5살');
const hisCat = new Cat('하얀색', '쵸비', '3살');

console.log(`내 고양이는 ${myCat.familyCat()}, 그의 고양이도 ${hisCat.familyCat()}입니다!`);
// "내 고양이는 하얀색 먼치킨, 그의 고양이도 하얀색 먼치킨입니다!"

✍️ prototype 프로퍼티를 이용하면 현재 존재하고 있는 프로토타입에 새로운 프로퍼티와 메소드를 추가하는 것도 가능하다.

직접 생성한 프로토타입은 위와 같이 새로운 프로퍼티나 메소드를 마음껏 추가하거나 삭제할 수 있다. (자바 표준 객체의 프로토타입도 수정가능하지만 오류가 발생할 수도 있으므로 하지 않는 것이 좋다고 한다.)




자바스크립트에는 Prototype Link 와 Prototype Object라는 것이 존재하며 이 둘을 통틀어 Prototype이라고 부른다.

예제를 통해 prorotype의 사용성에 대해 알아보았으니, 이제 조금 더 그 원리에 다가가보자. 🧐


(1) Prototype Object

Javascript에서 함수가 정의 될 때, 두가지 일이 발생한다.
1) Constructor(생성자)가 부여된다. Constructor 자격이 부여되면 new를 통해 객체를 만들어 낼 수 있게 된다.
2) 함수가 정의 되면, 함수 뿐 아니라 Prototype Object도 같이 생성된다.

이 두가지의 객체는 서로 연관되어 있기 때문에 서로를 알아야한다. ▼

  • 생성된 함수는 prototype이라는 property가 생기고 이를 통해 prototype object에 접근할 수 있다. (그래서 우리가 person.prototype 이라고 한다면 그림에서 오른쪽의 person prototype object를 가리키게 되는 것이다.)

  • 이제, 오른쪽의 person prototype object도 자신이 Person의 소속이라는 것을 표시하기 위해서 constructor 라고하는 프로퍼티를 만들고 그 프로퍼티는 좌측의 Person을 가리키게 된다.
    ( prototype object는 기본적인 속성으로 constructor__proto__를 가지고 있다. 또, Prototype Object는 일반적인 객체이므로 속성을 마음대로 추가 / 삭제 할 수 있다. )

이렇게, 서로간의 상호 참조를 하고 있는 것이다. (Person은 prototype을 통해서, Person Prototype Object는 constructor을 통해서)



function Person(name, first, second){
    this.name = name;
    this.first = first;
    this.second = second;
}

Person.prototype.sum = function(){}

const kim = new Person('kim',10,20);
const kim = new Person('lee',10,10);

console.log(kim.name);

7행 에서 이렇게 정의하면, Person의 prototype이 가리키는 오른쪽 prototype object에 sum이라는 프로퍼티를 생성하고 그곳에 함수를 정의한다.

이제부터 객체를 쭉쭉 생성한다.
9행 를 통해서 'kim' 이라는 객체를 생성하면 이 객체는 1행 의 constructor function이 실행 되면서, this 값이 설정된다. 그 결과 프로퍼티 값들이 생성이 됨과 동시에, __proto__ 가 생성이 되고, 이것은 자신을 생성한 함수(Person)의 프로토타입 객체를 가리키게된다.

prototype 속성은 함수만 가지고 있지만, __proto__ 속성은 모든 객체가 빠짐없이 가지고 있는 속성이다.

Person.prototypekim.__proto__ 를 통해서도 Person prototype object 에 접근할 수 있게된다.

12행 이제 우리가 콘솔을 통해 출력하면 'kim' 이라는 객체에 name있는지 보고 있다면 그 값을 출력, 없다면 __proto__ 가 가리키는 객체에 있는지 알아본다.
( 만약 여기에도 없다면 person prototype object도 결국 객체이므로 __proto__ 를 가진 것이기 때문에, 찾을 때 까지 상위 프로토타입을 탐색하게 되는 원리이다. )

▶ 이렇게 __proto__ 속성을 통해 상위 프로토타입과 연결되어있는 형태를 프로토타입 체인(Chain)이라고 한다. 이런 프로토타입 체인 구조 때문에 모든 객체는 Object의 자식이라고 불리고, Object Prototype Object에 있는 모든 속성을 사용할 수 있게되는 것이다.

JavaScript의 클래스를 공부하기 위해 프로토타입에 대해서 간단하게만 알아본 것인데도 연관되어 있는 키워드들이 굉장히 다양했고 그만큼 알아야 하는 것들이 많았다. 😅




reference
TCP_School coding_everybody JS_prototype insanehong_blog

profile
기억보단 기록을 ✨

0개의 댓글