자바스크립트 Prototype

CinnamonTree·2022년 6월 27일
0

Javascript

목록 보기
1/1

Java, C++과 같은 클래스 기반 객체지향 프로그래밍 언어와 달리 자바스크립트는 프로토타입 기반 객체지향 프로그래밍 언어이다. Javascript에서는 원래 클래스라는 개념이 없기 때문에 기존의 객체를 복사(clone)하여 새로운 객체를 생성하는 프로토타입 개념을 활용한다.(ECMAScript 6에서 클래스가 추가되었다). 이렇게 생성된 객체 역시 다른 객체를 생성하는데에 필요한 원형이 될 수 있다.

함수와 객체의 내부 구조

함수를 정의하고 파싱 단계에 들어가면, 내부적으로 수행되는 작업이 있다.
함수 멤버로 prototype속성이 있는데, 이 속성은 다른 곳에서 생성된 함수이름의 프로토타입 객체를 참조한다. 프로토타입 객체의 멤버인 constructor속성은 함수를 참조하는 내부 구조를 가진다.
또한 함수 원형으로부터 생성된 모든 객체는 프로토타입 객체를 참조한다.

프로토타입은 크게
1. prototype객체를 참조하는 함수 객체의 prototype속성
-> 함수와 new연산자가 만나 생성한 객체의 프로토타입 객체를 지정해주는 역할을 함
2. 객체 멤버인 proto 속성이 참조하는 숨은 링크
-> proto 프로퍼티에 접근하면 내부적으로 Object.getPrototypeOf가 호출되어 프로토타입 객체를 반환한다.
두 가지로 해석할 수 있다.

프로토타입 객체란

프로토타입 객체는 함수를 정의하면 다른 곳에서 생성되며, 다른 객체의 원형이 될 수 있다. 모든 객체는 프로토타입 객체에 'proto'를 통해 접근할 수 있다.

var student = {
  name: 'Lee',
  score: 90
}
console.log(student.__proto__ === Object.prototype); // true

프로토타입 객체도 동적으로 런타임에 멤버를 추가할 수 있다. 같은 원형을 복사하여 생성된 모든 객체들은 프로토타입 객체의 멤버를 새로 정의했을 때 해당 멤버에 대해 같은 값을 가지게 된다.

function Food(){} //함수 원형

var Taco = new Food();
var Pizza = new Food();

Food.prototype.getType = function(){
	return "음식";
}

console.log(Taco.getType()); //음식
console.log(Pizza.getType()); //음식

따라서 Prototype객체에 getType이란 함수를 추가하면, '멤버를 추가하기 전에 생성한 객체'에서도 추가된 해당 멤버를 사용할 수 있다. 프로토타입 객체에 멤버를 추가, 수정, 삭제 할 때는 함수 안의 Prototype속성을 사용해야 한다. 멤버를 읽을 때는 객체 이름으로 접근 가능하다.

코드의 재사용

Javascript는 프로토타입을 활용하여 코드를 재사용한다. 코드를 재사용하는 방식은 크게 classical 방식과 prototypal방식으로 나뉜다.

  • classical: new 연산자를 통해 객체 생성
  • prototypal: 리터럴 또는 Object.create()를 이용해 객체 생성 및 확장

classical 방식 예제

부모에 해당하는 함수를 이용해 객체를 생성한다.

예제 1

function Food(name){
    this.name = name || "맛있는 음식"
} //함수 원형

Food.prototype.getName = function(){ return this.name;}

function Italian(name){} 

Italian.prototype = new Food();

var pizza = new Italizan();
console.log(pizza.getName()); //맛있는 음식

var taco = new Italizan("타코");
console.log(taco.getName()); //맛있는 음식

위의 예제에서 함수의 프로토타입 속성을 부모 함수로 생성된 객체로 바꾸었다. 따라서 pizza와 taco의 'proto' 속성이 부모 함수를 이용하여 생성된 객체를 참조한다.

위 방식의 단점은 부모객체의 속성과 부모객체의 프로토타입 속성을 모두 물려받게 되고, 자식객체를 생성할 때 인자를 넘겨도 부모 객체를 생성할때의 인자로 생성된다. 객체를 생성할 때마다 부모 함수를 호출할 수 없기 때문에 비효율적이다.

예제 2: 생성자 빌려쓰기

function Food(name){
    this.name = name || "맛있는 음식"
} //함수 원형

Food.prototype.getName = function(){ return this.name;}

**function Italian(name){
	Food.apply(this, arguments);
} **

var taco = new Italizan("타코");
console.log(taco.getName()); //타코

Italian 함수 내부에서 apply함수를 이용하여, 부모 객체인 Food함수 영역의 this를 Italian함수 안의 this로 바인딩한다. 하지만 이러한 생성자 빌려쓰기 방식은 부모객체를 복사하여 자신의 것으로 만들어버려, 부모객체의 this로 된 멤버들만 물려받게 되는 단점이 있다. 그래서 부모객체의 프로토타입 객체의 멤버들을 물려받지 못한다.

예제 3: 생성자 빌려쓰고 프로토타입 지정

function Food(name){
    this.name = name || "맛있는 음식"
} //함수 원형

Food.prototype.getName = function(){ return this.name;}

function Italian(name){
	Food.apply(this, arguments);
} 
**Italian.prototype = new Food();
**
var taco = new Italizan("타코");
console.log(taco.getName()); //타코

이 방식은 부모 생성자를 두번 호출한다는 문제가 있다.

예제 4: 프로토타입 공유

function Food(name){
    this.name = name || "맛있는 음식"
} //함수 원형

Food.prototype.getName = function(){ return this.name;}

**function Italian(name){
	this.name = name;
} 
Italian.prototype = Food.prototype;**

var taco = new Italizan("타코");
console.log(taco.getName()); //타코

부모함수의 내용을 상속받지 못하므로 상속받으려는 부분을 프로토타입 객체에 작성해야 원하는 결과를 얻게 된다.

prototypal 방식 예제

Javascript는 prototypal방식을 더 선호나는데, 왜냐하면 classical한 방식보다 간결하게 구현할 수 있기 때문이다.

var food = {
	type : '음식'
    getType : function(){
    	return this.type;
    }
	getName : function(){
    	return this.name;
    }
}

var italian = Object.create(Food);
italian.name = '이탈리안';

Object.create()를 사용하여 객체 생성과 동시에 프로토타입 객체를 지정한다. 첫번째 매개변수로 부모객체로 사용할 객체를 넘겨주고, 쓰진 않았지만 두번째 매개변수는 선택적 매개변수로서 자식객체의 속성에 추가되는 부분이다.

출처: https://www.nextree.co.kr/p7323/

0개의 댓글