Web Front-end/JavaScript

[JS/Basic/객체] 자바스크립트 객체(new,factory,ProtoType)

OOP는 "학문"으로써 취급받을만큼 연구도 많이되었고, 현업에서 가장 많이 쓰는 형태입니다. 따라서, OOP에 대한 이야기를 하면 진짜 끝이 없기 때문에, 이번 글은 "자바스크립트에서 OOP를 어떻게 구현했는지"에 초점을 마추려고 합니다.

잠깐!

객체는 어떤 "사물"을 컴퓨터의 세계에 나타내기 위한 방법이라고 했습니다. 이번 글에서는 게임 속의 캐릭터를 나타내는 예시로 쭉 가겠습니다 :) 


객체 만들기

JS에서는 객체를 {} (중괄호 리터럴)으로 선언을 합니다 . 그리고 안의 값을 Property라고 하고 Property는 key와 value라고 했습니다.  "게임 캐릭터"에 필요한 Property는 무엇이 있을까요?

  • 닉네임
  • ID
  • 직업
  • State(스텟, 힘,지력, HP,MP ,방어력 등등 ) 

위의 값으로 작성해볼게요.(ID와 Name,직업(class)는 제 임의대로 작성하겠습니다)

const player={
	ID: "Mapins",
	name: "짱쎈머핀",
	class: "전사",
	state: {
            HP: 50000,
            MP: 1000,	
            STR: 50000,
            INT: 1000 ,
            DEF: 50000,	
	},
}

아주 훌륭한 유저 정보가 만들어졌습니다. 하지만, 중요한게 빠졌습니다. 게임캐릭터는 "행동"을 할 수 있어야합니다. 기본적인 "공격"기능과 "도망가기" 기능을 만들어 봅시다. 

객체의 value에는 함수도 들어갈 수 있다고 했죠? 객체안의 함수를 "메소드"라고 부릅니다. 용어니까 기억하고 갑시다.

메소드

  • 객체 안에 정의되어있는 함수
const player={
	ID: "Mapins",
	name: "짱쎈머핀",
	class: "전사",
	state: {
            HP: 50000,
            MP: 1000,	
            STR: 50000,
            INT: 1000 ,
            DEF: 50000,	
	},
    attack: function(){
    	console.log(" Mapin님이 공격을 시작합니다! ");
    }
    //아래와 같이 축약해서 사용할 수 있습니다.
    escape(){
    	console.log(" Mapin님이 도망을 갑니다! 쫄? ");
    } 
}
// 아래와 같이 선언해서 넣어줘도 됩니다.
player.attack = function () {
	...
}

좋습니다. 훌륭한 캐릭터가 만들어졌습니다. 하지만, 혼자 플레이하는 게임은 흔치않죠.  멀티 플레이는 기본입니다. 여러 명이서 플레이 하기위해서 다른 친구의 정보도 만들어 봅시다. 여기서, 여러분은 중요한 것을 눈치채야합니다.

const player={
	ID: "Mapins",
	name: "짱쎈머핀",
	class: "전사",
	state: {
            HP: 50000,
            MP: 1000,	
            STR: 50000,
            INT: 1000 ,
            DEF: 50000,	
	},
    attack: function(){
    	console.log(" Mapin님이 공격을 시작합니다! ");
    }
    escape : function(){
    	console.log(" Mapin님이 도망을 갑니다! 쫄? ");
    } 
}
const player2={
	ID: "Friends",
	name: "내가최강",
	class: "마법사",
	state: {
            HP: 10000,
            MP: 300000,	
            STR: 5000,
            INT: 200000,
            DEF: 5000,	
	},
    attack: function(){
    	console.log(" 내가 최강님이 공격을 시작합니다! ");
    }
    escape : function(){
    	console.log(" 내가 최강님이 도망을 갑니다! 쫄? ");
    } 
}

"반복" 되지않나요? 반복은 제거해야죠. 반복을 안하기 위해서 코드를 작성하니까요 :) 함수를 정의한다음, return { 플레이어 객체}로 해주면 반복이 제거되겠죠! 아주 훌륭한 코드입니다!

Factory Method

네, 맞습니다. 이 "함수"가 바로 생성자의 시작입니다. 디자인패턴 중, Factory 패턴이라고 합니다. 공장에서 물건을 찍어내듯, 객체를 찍어내는 Factory Function인거죠.

function makePlayer(_id,_name,_class,_state){
		return ({
        	    id:_id,
                name:_name,
                class:_class,
                state:_state,
                attack:function(){},
                escape:function(){},
        })
}
//Example Code
const player1 = makePlayer("mapin","Mapins","전사",{HP:50000...});
const player2 = makePlayer("친구","Friends","마법사",{HP:5000...});

생성자(Constructor)

  • 객체를 생성해주는 "생성자"의 역할은 필수적입니다. 그래서, 자주 쓰게되었고 결국 "생성자"라는 별도의 함수가 탄생하게 된거죠.
  • 몇가지 차이점이 있습니다. 위의 생성자의 "시작"과 다른점을 비교해보시죠!  
function MakePlayer(id,name,class,state){
        this.id=id;
        this.name=name;
        this.class=class;
        this.state =state;
        this.attack = function(){
            console.log(this.name + " 님이 공격을 합니다!");
        }
        this.escape = function(){
            console.log(this.name + " 님이 도망갑니다!");
        }
}
//example Code
const player1 = new MakePlayer("mapin","mapins"...)

내부적으로 다른 동작을 하겠지만, 일단 "코드"에서 달라보이는 걸 짚어보면 다음과 같습니다.

  • 생성자 내부에 this가 사용되었다. (참고로, 객체안의 this는 자기자신입니다)
  • 생성자의 첫시작 글자가 대문자이다.
  • new를 사용하였다. 

this(자기 참조변수)

  • 자기자신을 가리키는 "자기 참조"형태의 변수입니다. 
  • 객체 안의 "this"는 우선 자기자신이라고 생각하시면 됩니다.
  • 각각의 객체에 만들어지는 attack과 escape, 속성값들은 모두 달라지기 때문에 , this가 필수적이게 됩니다. (player1과 player2의 아이디,이름 등은 달라야 하니까요!)
  • 외부 참조로 인한 오류를 방지할 수 있습니다.
  • JS에서 "this"는 런타임에서 this가 결정됩니다. 객체선언에 종속적이지 않습니다. (이 부분은 나중에 따로 적겠습니다. 지금은 객체에 집중해주세요!)

* 외부참조를 이용한다면 ?

let player= {
        name:"mapins",
        class:"전사",
        attack(){
            console.log(player.name + " 님이 공격합니다!");
        }
}

let mapins1 = player;
player= null;

//this code is error!!
mapins1.attack()

* this를 이용한다면? 

let player= {
        name:"mapins",
        class:"전사",
        attack(){
            console.log(this.name + " 님이 공격합니다!");
        }
}

let mapins1 = player;
player= null;

mapins1.attack()

프로토타입(ProtoType)

  • 생성자로 객체를 생성하는것 좋았는데, 개발자들은 생성자에서 "효율적이지 못한" 부분을 발견하게 됩니다. 바로, 공통적인 부분들을 계속해서 써야한다는 점이었습니다. 저희가 계속 예를들었던 코드에서는 attack과 escape정도가 있겠네요. 다른 정보들은 캐릭마다 다르니까요! 
  • 그래서, JS에서는 ProtoType의 개념을 이용해서, 객체의 원형(ProtoType)을 정의하고 그 값을 참조하는 형태로 바꾸었습니다.  심지어 공통된 속성이 많다면 집어넣어도 됩니다. 메모리는 한정적이니까요. (디자인 패턴 중 , "플라이급 패턴"을 찾아보시면 될겁니다.)
  • ProtoType을 참조하여, 객체에 __proto__를 추가하게 됩니다.
function Player(id,name,class,state){
        this.id=id;
        this.name=name;
        this.class=class;
        this.state =state;
        /*
        this.attack = function(){
            console.log(this.name + " 님이 공격을 합니다!");
        }
        this.escape = function(){
            console.log(this.name + " 님이 도망갑니다!");
        }*/
}
Player.prototype.attack = function(){
	console.log(this.name + " 님이 공격을 합니다!");
}
Player.prototype.escape = function(){
	console.log(this.name + " 님이 도망갑니다!");
}