Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

도도의 IT이야기

[자바스크립트 기본]객체,생성자,프로토타입,프로토타입 체인 본문

IT/자바스크립트

[자바스크립트 기본]객체,생성자,프로토타입,프로토타입 체인

도도버드 2020. 5. 8. 19:09

자바스크립트는 객체지향 프로그램이라는 말이 있을 정도로 자바스크립트에서 객체는 굉장히 중요한 부분을 차지합니다.

 

객체(Object) 뜻

 

우선 객체란 말이 너무 어렵군요. 저는 코딩을 공부하기 전에 객체라는 단어가 존재하는지도 몰랐습니다.

 

객체를 알기위해선 객체가 영어로 무엇인지 알아야합니다. 객체는 영어로 "Object" 즉 물건이라는 뜻 입니다. 그렇습니다. 객체란 물건을 의미하는 것이죠. 이게 무슨 개소리야? 이런 소리를 하신 분들도 있으겁니다. 그러니 이해하기 쉽게 코드를 예로 들면서 설명해봅시다.

 

그 전에 한 가지 물건이나 사람 아무거나 떠올려 봅시다. 마침 밖에 차가 있으니 저는 차를 선택하겠습니다.

 

그럼 밖에 있는 차에 대해 설명을 해보겠습니다

1. 바퀴가 네개있다.

2. 차는 검정색이다.

3. 시동이 꺼져있다.

 

이걸 코드로 옮기면

let wheels = 4;//바퀴가 네개이며

let color = "black";//색은 검정색

let engineOn = false;//시동이 꺼져있음

이렇게 됩니다. 

 

하지만 이 특징들이 무슨 물건의 특징인지 이 코드만 보는 사람들은 이해를 못하것입니다.

그래서 "차"라는 하나의 물건의 특징들을 차라는 Object(물건)에 묶어 봅시다.

 

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : false//시동이 꺼져있음
   };

 

car라는 object에 속성들을 옮기고 나니 "아~ 저 특징들이 차의 특징이었구나"라고 누구나 알 수 있습니다. 

 

하지만 밖에있는 차의 특징 저것들 밖에 없는 것이 아닙니다. 차는 엑셀을 밟으면 앞으로 나가죠. 이것도 하면 쳐봅시다.

 

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : false,//시동이 꺼져있음
   excel : function(){
   	return "부릉부릉";
   }
  };

 

차의 엑셀을 밟으면 "부릉부릉" 앞으로 나가는 기능도 추가했습니다.

 

다시 코드를 보면 car이라는 object안에 속성들을 key : value 또는 property : value 페어로 정의한 것을 볼 수 있습니다. 

 

하지만 excel의 value는 함수죠? 이렇게 object안에서 특정한 로직을 실행하거나 어떠한 값을 return하고 싶을때에는 value에 함수를 쓰고 value를 함수로 갖는 property(속성)를 method(메소드)라고 부릅니다.

 

객체 속 접근방법

우리가 위에 만든 car 객체의 색이 알고 싶을때엔 어떻게 할까요?

그냥 color 라고 하면 "black"이 나올까요? 

아닙니다. 객체 안에 있는 속성값에 접근하려면 두가지 방법을 사용해야 합니다.

 

1. dot(.) notation

2. bracket([ ]) notation

 

dot notation과 bracket notation은 비슷하지만 bracket notation은 변수를 담을 수 있다는 점에서 더 활용도가 높습니다.

 

코드로 예제를 들어보죠

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : false,//시동이 꺼져있음
   excel : function(){
   	return "부릉부릉";
   }
  };
  
  car.color // black
  car.['color'] // black
  
  let x = "wheels"//변수를 사용해보자
  
  car.x // undefined
  car[x] // 4

 

그러니 객체이름.속성 또는 객체이름['속성'] 이런 느낌입니다.

 

객체와 this

우리는 이전에 car 객체에 엑셀을 누르면 "부릉부릉"을 return하는 메소드를 정의했습니다. 근데 차가 시동이 꺼져있다면 엑셀을 아무리 눌러도 차는 움직이지 않을 것입니다. 그러니 차의 시동이 켜져있으면 "부릉부릉"을 return하는 메소드로 바꿔 봅시다.

 

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : true,//시동이 켜졌음
   excel : function(){
   	if(this.engineOn){
   	  return "부릉부릉";
    }
   }
  };
  
 car.excel();//부릉부릉

if 조건물을 이용해 차의 시동이 켜져있으면 나가는 메소드로 바꿨습니다.

여기서 주목해야 될점은 우리가 같은 객체안에 있는 속성값이 필요할때 

car.engineOn 이라고 한게 아니라 this.engineOn 이라고 했습니다. 

 

this가 뭐냐?

this는 굉장히 깊고 어려운 토픽입니다. 사실 저도 완전하게 모르고요.

지금은 그저 객체안에서 this가 사용되면 this는 자신이 속해있는 객체를 가리키는 포인터라고 생각하면 편합니다.

 

car.engineOn과 같은 결과를 도출하지만 car의 이름을 나중에 superCar이렇게 바꿨을때 문제가 됩니다.

 

*this와 ES6 arrow 함수*

 

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : true,//시동이 꺼져있음
   excel : () => {
   if(this.engineOn){
      return "부릉부릉";
      console.log(this);//window를 카리킴
    }
   }
  };
  
  car.excel();//undefined
  

arrow 함수안에 this를 쓰면 this가 객체를 가리키는게 아니라 전역 범위인 window를 가리키기 때문에 this를 쓸때는 arrow함수를 쓰면 안됩니다.

 

이유는? 모르겠습니다. arrow함수 안에서는 왜인지 몰라도 this가 객체로 bound가 안되는거 같음.

 

짧게 쓰고 싶다면 이렇게 쓰면 됩니다.

let car = {
   wheels : 4,//바퀴가 네개이며
   color : "black",//색은 검정색
   engineOn : true,//시동이 꺼져있음
   excel(){//요로케!
   if(this.engineOn){
      return "부릉부릉";
      console.log(this);//car을 가리킴
    }
   }
  };

  car.excel();//부릉부릉

추가로 ES6에서 property 쇼트컷

let name = "mike";
let feel = "happy";

let human={
  name : name,//속성의 name과 위의 변수의 name이 같지만 속성이 "mike"가 되지는 않는다. 
  [name] : name, 
  feel//feel : feel
};

human.name;//mike
human[name];// === human.mike
human.feel//happy

//object의 property를 변수의 값으로 부르고 싶을때 object.[변수이름]
//object의 property를 변수의 값으로 정의하고 싶을때 {[변수이름]:value}
//그러니까 name : name 은 name : 'mike' 와 같고
// [name] : name 은 'mike' : 'mike' 와 같다

외워야될점: key의 이름은 []bracket notation을 사용하기 전까진 unique하다! 즉 parent scope에 key와 같은 이름의 변수가 있어도 key의 이름이 변수 값으로 바뀌지 않음.

 

객체를 생성하는 4가지 방법

이제 객체를 생성하는 방법에 대해 알아봅시다.

 

1. literal notation

2. constructors and factory function

3. Object.create() method

4. class

 

1. Literal notation

우선 1번 리터럴 표기법은 우리가 지금까지 사용한 방법입니다

var obj = {key:value,key:value}; <== 이런 방법이죠.

여기서 {key:value,key:value}을 object literal / 객체 리터럴 이라고 부릅니다.

 

하지만 literal notation은 큰 취약점이 있습니다. 그걸 보여드리겠습니다.

 

위의 예제에서 우리가 car 객체를 만들었습니다. 

이제 car 옆에있는 car2도 객체로 만들어 볼까요?

 

let car = {
   wheels : 4,
   color : "black",
   engineOn : false,
   excel : () => {
   if(this.engineOn){
      	return "부릉부릉";
    }
   }
  };
  
let car2 = {
   wheels : 4,
   color : "blue",
   engineOn : false,
   excel : () => {
   if(this.engineOn){
      	return "부릉부릉";
    }
   }
  };
  

car2 객체도 만들었습니다.

 

근데 눈치 채셨나요? car1과 car2는 색을 제외하고는 완전하게 같은 객체입니다.

만약에 차가 두대말고 1000대가 있다고 상상해봅시다. 그럼 우리는 비슷비슷한 코드를 1000개나 쳐야된다는 의미이고 그것은 굉장히 비효율적입니다.

 

그래서 우리는 이제부터 자동차 공장을 하나 만들겁니다.

 

그 자동차 공장은 아직 도색이 안된 무색의 자동차를 준비 해두고 기다립니다. 내가 "빨간색 자동차 주세요"라고 하면 그제서야 자동차를 빨간색으로 칠하고 저에게 줄것입니다.

 

2. Constructor(생성자)와 factory 함수

굉장히 긴 토픽이 될거 같습니다 설명할게 아주아주 많거든요.

 

우선 constructor과 factory는 둘다 객체를 만드는 공장이라고 생각하시면 좋을거 같습니다. 

 

factory는 나중에 다시 설명하도록 하고 constructor(생성자)에 집중합시다.

 

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}

let car1 = new Car('blue','Hyundai');

 

이게 constructor 함수입니다 하나하나씩 봅시다

 

1. 맨 밑에서 부터 시작합시다. 먼저 car1 이라는 변수가 선언되었고 오른쪽에있는 new Car('blue','Hyundai')의 값으로 초기화될 준비를 완료합니다.

2. 그럼 new는 무엇을 하는거냐? 여러가지 일을 하지만 여기서는 우선 비어있는 object {} 을 만듭니다. 그리고 옆에있는 Car 생성자 함수의 this를 그 object에 포인트하게 합니다. 다시 한번 말하자면 이제 Car안에 있는 this는 new가 만든 {}를 가리키는 포인터가 된겁니다.

let car1 = new Car('blue','hyundai');

//new가 {   }(비어있는 객체)를 만들고 Car 함수의 this를 저 객체로 "bind"함

3. 이제 드디어 Car 함수를 호출하고 두개의 인자들을 파라미터로 전송합시다.

4. Car 생성자의 첫번째 줄을 봅시다.

this.color = color

여기 this가 나왔네요 아까전에 this는 이제 무엇을 가리킨다고 했죠? 맞습니다 new가 만든 {}을 가리킵니다. 그래서 this.color 는 object literal 안에 color라는 속성을 만들고 우측에 있는 값으로 초기화 합니다. 그럼 우측에 있는 color의 값은 무엇일까요? 바로 우리가 매개변수를 통해 받은 'blue'라는 값입니다

{color:'blue'}

 

이런 결과가 나오네요 밑에있는 줄에도 같은 프로세스를 하면

{color:'blue',brand:'hyundai'}

 이제 함수에서 나온 값으로 car1을 초기화 시키면

let car 1 = {color:'blue',brand:'hyundai'};
//객체 완성!

객체를 완성시킬 수 있습니다.

 

만약 파랑색 현대차 말고 검정색 벤츠를 만들고 싶으면 이렇게 하면 됩니다.

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}
let car1 = new Car('blue','Hyundai');
let car2 = new Car('black','Benz');

이렇게 우리는 여러 객체들을 손쉽게 많이 만들 수 있습니다.

 

여기서 만들어진 car1 과 car2를 Car 생성자의 인스턴스(instance)라고 합니다.

그러니까 객체들은 그들을 만든 생성자의 instance가 되는 것입니다.

 

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}
let car1 = new Car('blue','Hyundai');
let car2 = new Car('black','Benz');

car1 instanceof Car // true
car2 instanceof Car // true

위처럼 instanceof를 이용해 알 수 있습니다.

 

construtor로 객체를 생성하는 방법은 이제 알았으니 프로토타입 prototype 대해서 자세히 알아봅시다.

 

Prototype 프로토타입

 

Car 생성자 함수로 돌아가서 이번에 차의 바퀴의 수와 엑셀 메소드를 추가합시다.

function Car(color,brand){
    this.color = color;
    this.brand = brand;
    this.tire = 4;//타이어 4개
    this.excel=function(){return "부릉부릉"};//엑셀 기능 추가
}
let car1 = new Car('blue','Hyundai');
let car2 = new Car('black','Benz');

이제 car1과 car2에 속성과 메소드를 각각 추가했습니다.

car1 = {tire:4, excel:function(){return 부릉부릉}}//추가됨
car2 = {tire:4, excel:function(){return 부릉부릉}}//추가됨

하지만 모든차는 타이어가 4개고 엑셀기능이 있습니다. 그래서 우리는 완전히 같은 코드를 두번이 친것입니다. 프로그래밍은 반복을 굉장히 싫어하는 언어니 이건 문제입니다. 

 

우리는 코드의 량을 줄이기 위해 instance인스턴스들이 공유하는 속성들과 메소드들을 생성자의 프로토타입이라는 곳에 모아둘 수 있습니다. 그리고 인스턴스들은 그 프로토타입의 속성들과 메소드를 상속하는 것입니다.

 

그럼 프로토타입은 뭐고 프로토타입은 어디에 어떻게 존재하는 거냐?

 

프로토타입은 생성자를 포함한 모든 함수들이 가지고 있는 특수한 객체를 뜻합니다.

 

좀더 자세히 설명하자면 자바스크립트에서 존재하는 모든 함수들은 자바스크립트의 빌트인 생성자인 Function 생성자에 의하여 만들어집니다. 우리가 직접적으로 Function 생성자를 사용하지 않고 그냥 함수를 선언할때에도 자바스크립트는 우리에게 안보이는 곳에서 Function 생성자로 그 함수를 만든 것입니다.

   new Function('return x;');//이녀석은 Function 생성자를 직접적으로 사용해서 만들어짐

   function f(){//이녀석은 직접 사용하지 않지만 자바스크립트가 안보이는 곳에서 위에처럼 처리함
      return x;
   }

   아 아건 상관없는 내용이지만 선언한 함수와 Function으로 생성한 함수는 차이점이 존재합니다.

let x=10;

function a(){
   let x = 5;
   return new Function('return x');//Function으로 직접 생성된건 언제나 전역범위에서 만들어진걸로 함
   //그래서 로컬 변수인 x=5를 리턴하지 않고 전역 변수 x=10을 return함 (클로져가 없음)
}

function b(){
   let x = 7;
      function f(){return x;}//선언된 함수는 클로져가 있음 함수가 선언된 곳에 있는 변수들에게 접근가능 
   return f;
}

a()()//10
b()()//7

다시 본론으로 들어와서 모든 함수들은 Function생성자에 의해 만들어지고 Function 생성자가 함수를 만들때 자동적으로 프로토타입이라는 객체를 넣어줍니다.

위에 보이다시피 우리는 함수를 선언했을 뿐인데 함수안에 자동적으로 프로토타입이라는 객체가 추가된것을 볼 수 있습니다. 그리고 이 프로토타입이라는 객체안에는 constructor 라는 속성이 있습니다. 이 constructor속성은 프로토타입이 위치한 함수를 가리킵니다. 그러니까 위의 예제에서 프로토타입의 constructor속성은 example함수를 가리키는 포인터겠죠. 더 쉬운 설명은 A함수가 만들어질때 프로토타입이라는 객체가 A안에 생성되고 A함수는 프로토타입의 constructor속성에 자기 자신을 저장해놓는겁니다. constructor속성의 목적은 곧 설명드리겠습니다.

 

우리가 평소 함수를 쓸때에는 이 프로토타입이나 안에있는 constructor속성에 대해 전혀 알고 있을 필요가 없습니다. 필요도 없고요. 하지만! 우리가 함수를 생성자로써 사용할때에는 이야기가 달라집니다. 한번 생성자 함수를 만들어보고 우리에게 보이지는 않지만 자바스크립트가 우리를 위해 넣어둔 프로토타입 객체도 우리눈에 보이게 해봅시다.

 

그런 이제 저 Girl생성자로 객체 인스턴스를 만들어 봅시다.

 

우리는 아까 정의한 Girl 생성자로 girl1객체를 만들었습니다. 이제 girl1은 인자로 전달한 "elsa"라는 값이 저장된 name속성을 가지고 있습니다.

 

Girl 생성자는 하나의 속성(name)을 파라미터로 받은 name의 값으로 지정하는 생성자입니다. 즉, Girl 생성자로 만들어진 girl1객체에는 하나의 속성만 있어야 될거 같지만 실은 다른 속성이 하나 더 있습니다. Girl 생성자의 프로토타입 객체 안에 있는 속성들을 상속해서 constructor 속성을 받은 것입니다. 받은 constructor 속성에는 생성자 함수가 나와있습니다. 그러므로 다시 한번 정의하자면 constructor속성은 객체를 생성한 생성자를 가리키는 포인터입니다.

 

girl1.constructor//이렇게 치면 Girl함수를 가리키는 reference가 나올것이다

 상속이란 말이 어렵다면 그냥 이렇게 생각해도 됩니다. 생성자가 인스턴스를 만들때 프로토타입 안에 있는 것들을 복사해서 인스턴스에 붙여넣기 한다.

 

**근데 constructor 속성은 overwrite가 될수있기 때문에 A객체가 B생성자의 인스턴스가 맞는지 아닌지를 볼려면 instaceof를 사용하자(이따가 더 깊게 다룰것이다)

 

그래서 우리는 어떠한 객체를 만든 생성자가 누구인지 prototype으로 상속된 constructor속성으로 알 수 있습니다. 근데 우리는 consturctor 속성 말고도 상속될 속성들을 마음대로 추가할 수 있습니다.

 

이것이 프로토타입의 주 목적입니다. 모든 객체들이 공유하는 공통점(속성)이나 행동(메소드)들을 프로토타입에 담아놓으면 인스턴스들이 그것을 사용할 수 있습니다.

 

그럼 프로토타입에 어떻게 속성들을 추가하냐?

 

그냥 우리가 다른 모든 객체 속성을 추가하듯 하면 됩니다.

 

위의 Car 생성자를 다시 불러옵시다.

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}

Car.prototype.tire = 4;//모든 인스턴스가 공유할 속성
Car.prototype.excel=function(){return "부릉부릉"};//공유할 메소드


let car1 = new Car('blue','Hyundai');
let car2 = new Car('black','Benz');

생성자이름.prototype.속성 = 값 

 

이런 방식으로 prototype객체에 속성을 추가할 수 있습니다.

 

이렇게 생성자로 만들어진 인스턴스들은 두가지 속성이 있습니다. 

1. 자기 자신에게 unique 유니크한 own property 자가속성

2. 다른 인스턴스들과 공유하는 prototype property 프로토타입 속성

 

만약 어떤 속성이 자가 속성인지 프로토타입 속성인지 알고싶다면 hasOwnProperty메소드를 사용하면 됩니다.

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}
Car.prototype.tire = 4;
Car.prototype.excel=function(){return "부릉부릉"};

let car1 = new Car('blue','Hyundai');

car1.hasOwnPropery('color');//true 
//이렇게 내장되있는 메소드 hasOwnPropery를 이용해서 보낸 인자가 object의 own property인지 알 수 있음

let ownProp = [];
let proProp = [];
for(let x in car1){
  if(car1.hasOwnPropery(x)){
  	ownProp.push(x);//['color','brand']
  }else{
  	proProp.push(x);//['tire','excel']
  }
}

 

위의 Car constructor 예제에서는 차들이 공유하는 prototype 객체안에 tire 과 excel 메소드를 추가했습니다. 하지만 실제로 차들이 공유하는 속성들은 너무나 많습니다. 브레이크, 라이트, 와이퍼, 엔진 등등이 있죠. 이것을 하나하나 추가하면 이런 형식이 될겁니다.

Car.prototype.wiper = ;
Car.prototype.engine = ;
Car.prototype.handle = ;
Car.prototype.light = ;
Car.prototype.break = ;

이렇게 수많은 prototype 속성들을 하나하나 넣는 것은 너무 시간이 많이 걸리는 일입니다. 그렇기에 우리는 속성을 추가 하는 방법이 아닌 prototype 객체를 reinitialize 하는 방법이 있습니다.

 

 

Car.prototype = {
   tire : 4,
   excel: function(){return '부릉부릉'},
   break: function(){return '끼이익'},
   engine: "가솔린"

}

이런 식으로 말이죠. 하지만 우리는 속성을 추가한게 아니라 prototype 객체를 초기화 한것입니다. 그러므로 원래 prototype안에 있는 constructor 속성을 지워버리게 되죠. 그러니 prototype을 reinitialize 할때는 constructor 속성을 꼭 넣어주도록 합시다.

Car.prototype = {
   constructor:Car,
   tire : 4,
   excel: function(){return '부릉부릉'},
   break: function(){return '끼이익'},
   engine: "가솔린"

}
Car.prototype.isPrototypeOf(car1)//true
//생성자의 prototype객체가 다른 객체의 프로토타입인지 알고싶을떄
//안중요함

Prototype Chian (프토토타입 체인)

이제 드디어 프로토타입 체인에 도착했습니다. 프로토타입은 알아도 프로토타입 체인은 뭔데? 이런 소리가 나오실겁니다. 지금부터 천천히 설명드릴게요.

 

우선 프로토타입을 다시 보죠.

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}
Car.prototype.tire = 4;
Car.prototype.excel=function(){return "부릉부릉"};

let car1 = new Car('blue','Hyundai');

이러한 코드가 있다고 합시다. 우리는 car1이라는 객체를 만들었습니다.

앞서 설명한대로 car1객체는 Car 생성자의 프로토타입의 속성들을 상속받습니다.

그럼 car1의 속성은 전부해서 몇개 일까요?

 

own property 자가속성 2개

1. color

2. brand

 

prototype property 프로토타입 속성 2개

1. tire

2. excel

 

이렇게 해서 총 4개의 속성들이 있습니다. 근데 이상한 점이 하나 있습니다.

 

우리는 아까 객체의 어떠한 속성이 own Property인지 알려면 어떻게했죠?

car1.hasOwnProperty('color');//true
car1.hasOwnProperty('tire');//false

이러한 car1에있는 hasOnwProperty 메소드를 사용했습니다.

 

이상합니다. car1안에는 우리가 지정한 4개의 속성들 밖에 없는데 hasOwnProperty는 어디서 나온것이지?

 

다시한번 말하지만 우리는 hasOwnProperty라는 메소드를 정의한적이 없습니다. 그럼 어디서 나온것일까요? 

 

쉽게 말하면 그냥 내장되어있는 메소드라고 얘기 할 수 있지만 도데체 어디에 내장되어 있는지에 대해 자세히 알아보겠습니다.

 

 

그전에 이해를 돕기 위해서 __proto__라는 속성 대해서 알아봅시다

 

__proto__

 

너는 뭐니? 일단 구글 제작자 툴에서 우리가 아까 Car생성자로 만든 car1객체를 쳐봅시다

 

결과는 color과 brand 두개의 own property 자가속성들만 포함된 객체입니다. 어? 그럼 프로토타입으로 상속된 건 어디갔을까? 이런 질문이 듭니다.

 

우선 상속받은 속성들은 겉으로는 보이지 않습니다. 왜냐하면 프로토타입 속성들을 "상속"한거지 "복사 붙여넣기'한게 아니기 때문입니다. 헷갈리시다면 예를 들어보겠습니다. 저에게 "나니아 연대기"와 "해리포터"라는 책이 있다고 해봅시다. 이것들은 저의 책이기 때문에 저만 읽을 수 있습니다. 하지만 저는 이 두책만 읽을 수 있는 것이 아닙니다. 제가 도서관까지 간다면 거기에서 수많은 책들을 읽을 수 있습니다. 하지만 도서관에 있는 책들을 제가 자유롭게 읽을 수 있다고 해서 도서관 책들이 제 것이 되는것이 아닙니다. 그러므로 prototype 속성들을 도서관의 책들이라고 생각하고 우리는 그들을 아무때나 사용할 수 있지만 prototype속성들은 모두를 위한 것이므로 저의 개인 책 목록에는 나와 있지 않습니다.

 

그럼 프로토타입으로 상속받은 속성들이 안나와 있다면 객체가 어떤 속성을 상속받았는지 어떻게 알 수 있을까요?

 

거기서 바로 __proto__포인터의 진가가 나오는겁니다. (MDN이나 다른 문서들을 읽어보니 depracated여서 쓰지 말라고 하지만 왠지 모르게 크롬이나 다른 웹브라우저들은 계속 사용하는거 같습니다)

 

__proto__ 속성은 쉽게 말해서 객체가 상속받고 있는 프로토타입 객체를 나타냅니다.

그냥 prototype 속성과 다른점은 뽑자면

 

  • prototpye 속성은 상속프로토타입 객체를 나타내고(생성자 함수들이 가지고 있는 속성)
  • __proto__속성은 상속받은 프로토타입 객체를 가리킵니다.(생성자 함수로 만들어진 객체들이 가지고 있는 속성)

 

prototype 속성은 아까전에 말했듯이 JS 빌트인 생성자 Function이 함수를 만들때 자동으로 함수안에 포함시키는 속성입니다.

 

그럼 __proto__어디서 나온걸까요? __proto__는 우리가 생성자 함수로 객체를 만들때 사용하는 키워드 new 로 만든것입니다. new의 기능이 참 많죠? 이따가 다 정리 하도록 하겠습니다.

 

그럼 위의 예제로 돌아가서 우리가 car1을 구글 콘솔에 쳤을때 own property인 brand와 color이 나옵니다 그리고 new 키워드가 우리를 위해 추가해준 __proto__도 볼 수 있습니다.

 

그럼 __proto__ 안에는 당연히 우리가 상속받은 프로토타입 속성들이 있겠죠? 맞습니다.

 

보이다 시피 __proto__안에는 우리가 추가한 excel method와 tire property가 있고 자동적으로 프로토타입에 존재하는 constructor속성이 있는것을 볼수 있습니다.

 

근데 우리가 집중해야 될것은 이 부분입니다. car1이 상속받은 프로토타입 객체 안에 또다른 __proto__속성이 있습니다!

 

__proto__는 상속받은 프로토타입 객체를 가르키는 겁니다. 그말은 car1이 상속받은 프로토타입 객체또한 어떠한 생성자에 의해 만들어졌고 그 어떠한 생정자로부터 상속받은 또 다른 프로토타입 객체가 있다는 뜻입니다.

 

프로토타입 객체도 상속을 받는다!

 

이걸 그림으로 봅시다

Car 생성자 안에 있는 프로토타입 객체는 object이므로 자바스크립트 빌트인 생성자인 Object에 의해 만들어집니다. 그러므로 Car의 프로토타입 객체는 Object의 인스턴스라고 할 수 있습니다. 그리고 생성자가 만든 새로운 인스턴스들은 모두 생성자의 프로토타입 객체를 상속받습니다. 그래서 Object생성자의 프로토타입 안에있는 hasOwnProperty(), isPropertyOf()등과 같은 메소드와 속성들이 모두 Car 프로토타입 객체안에 상속되어진 것입니다.

 

이제 Car 생성자가 car1이라는 객체를 만들때 car1은 Object 생성자의 프로토타입을 포함한 Car 생성자의 프로토타입을 상속받게됩니다.

 

이걸 프로토타입 체인이라고 하는건데  헷갈리네요. 제가 쳤지만 제가 이해를 못하겠습니다 그래서 조금더 말을 풀어서 다시 설명합시다.

 

 

 

객체가 생성자를 통해 만들어지는 과정

 

1. 우리가 생성자 함수 Car을 정의한다.

2. 이때 빌트인 Function 생성자가 Car 함수를 만든다.

let Car = new Function('doSomething');
//자바스크립트는 함수들을 이렇게 Function생성자에게 보내 밑에있는 함수를 만든다
function Car(){doSomething}

3. 생성된 Car 함수는 Function의 인스턴스 이므로 프로토타입을 상속받는다.

 

만들어진 Car 함수도 객체입니다. 만들어진 객체는 자신을 만든 생성자의 프로토타입을 상속받습니다.

 

빌트인 Function의 프로토타입 안에는 Function.argument 등의 속성이 있는거 같지만 많이는 안 쓰이는거 같습니다

 

함수를 제외한 다른 객체들은 이 과정으로 끝이납니다. 실제로 우리가 함수가 아닌 객체를 정의하면 빌트인 생성자인 Object는 우리가 특정한 값으로 객체를 만듭니다. 그리고 만들어진 객체는 Object 생성자의 프로토타입 객체를 상속받고 모든 과정이 끝이납니다. 하지만! 함수를 만들땐 함수만 만드는게 아니라 함수안에 있는 프로토타입 객체도 만들어집니다.

 

4. Function 생성자는 Car 생성자 함수를 만든 후에 자기 형인 Object 생성자를 부른다. 그리고 이런 말은 한다 "형! 나 지금 함수 만들었는데 그 안에 들어갈 프로토타입 객체가 필요해! 하나 만들어줘"

 

Object 생성자는 Car 생성자 안에 프로토타입 객체를 만들고 안에 own property로 Car 생성자를 가리키는 constructor속성을 추가합니다. 그리고 역시 Car 생성자 안에 만들어진 프로토타입 객체 또한 Object 생성자의 인스턴스 이기에 생성자의 프로토타입을 상속받습니다.

 

5. 이제 Car 생성자가 어떠한 객체를 만들때, 인자로 전달된 값으로 객체들을 만듭니다. 그리고 만들어진 instance들은 (Object 생성자의 프로토타입을 포함한) Car생성자의 프로토타입을 상속합니다.

 

휴... 어려운 내용이지만 조금이라도 도움되었으면 좋겠습니다.

 

 

이제 드디어 약간씩 언급했던 new의 역활에 대해 자세히 알아볼 시간이 된거 같습니다.

 

new의 역활

function Car(color,brand){
    this.color = color;
    this.brand = brand;
}

Car.prototype.tire = 4;//모든 인스턴스가 공유할 속성
Car.prototype.excel=function(){return "부릉부릉"};//공유할 메소드


let car1 = new Car('blue','Hyundai');//new를 왜 사용할까?

마지막 줄을 봅시다. car1이라는 Car의 인스턴스를 만드는 코드입니다.

 

우선 Car('blue','Hyundai')는 당연히 함수를 호출하는 것 입니다.

그럼 new는 도데체 뭘하는 것일까요? 우리 눈에는 보이지 않지만 new는 4가지 일을 합니다

 

1. {} 비어있는 객체를 만들고 생성자 함수의 this를 만든 객체로 bind 한다

 

new가 이 작업을 해주기 때문에 생성자 안에 있는 this.name = name 이 새로운 객체안에 속성을 만들 수 있는 것입니다. 그래서 new 덕분에 만들어진 결과를 보여드리죠

 

{
color:"blue",
brand:"hyundai"
}

 

2. 생성자의 prototype 속성들을 객체안의 __proto__ 속성에 집어 넣는다

 

위의 객체는 own property는 있지만 아직 프로토타입을 상속받지 않았습니다. new 키워드는 만들어진 인스턴스안에 __proto__속성을 만들고 생성자의 프로토타입을 가리키게합니다. 즉, 이 작업을 상속이 완료되는거죠.

 

아 __proto__포인터는 겉으로 보이지는 않습니다. 근데 우리는 인스턴스안에 생성자의 프로토타입을 가르키는 어떠한 포인터가 있다는 사실을 알아야합니다.

{
color:"blue",
brand:"hyundai"

__proto__//프로토타입을 가르키는 포인터
}

 

3. 함수, 객체, 배열의 리턴 값이 아닌 다른 값을 리턴 했을때 this(객체를 가리키는 포인터)를 반환합니다.

 

4. 리턴 값이 없다면 자동으로 this를 리턴한다

이 이유로 우리가 생성자 함수에 따로 리턴값을 지정할 필요가 없는 것입니다.

 

 

인스턴스 메소드 vs 정적 메소드

우선 빌트인 생성자인 Object를 분석해 봅시다.

 

빌트인 Object 생성자안에 있는 코드들은 세가지로 분류할수 있다.

1. 주어진 값으로 객체를 만들때 사용되는 constructor 코드

2. 만들어진 객체에 상속할 프로토타입 객체

3. Object 생성자가 고유로 가지고 있는 정적 메소드와 속성들

 

1번은 당연하죠? 생성자니까 생성하는 코드가 있을거고

2번은 지금까지 엄청나게 설명한 인스턴스에 상속되는 프로토타입입니다

 

3번은 나중에 얘기합시다

 

1번과 2번으로 생성자가 자신의 인스턴스에게 생성하거나 상속해준 속성이나 메소드들을 인스턴스 메소드라고 합니다. 이름이 말하듯 이 속성과 메소드들은 인스턴스들이 사용하는 거겠죠. 예를 들면(Object로 설명 드리고 있었지만 Array에 자주쓰는 메소드들이 많기 때문에 예제는 Array로 하겠습니다)

 

let arr=[1,6,3,2];
arr.sort()//[1,2,3,6]

 

Array 생성자의 인스턴스인 배열 arr은 Array 생성자의 프로토타입 객체안에 있는 sort()를 상속받았습니다.

그렇기에 sort() 메소드를 인스턴스 메소드라고 합니다.

 

 즉,  (프로토타입으로 상속받아) 인스턴스로부터 access(접근)할 수 있는 메소드나 속성들을 인스턴스 속성/메소드라고 합니다.

 

그럼 3번은 뭘까요?

 

Array생성자의 주 업무는 새로운 배열을 만들고 그 배열에게 자신의 프로토타입을 상속하는 역활을 하지만 생성자 또한 객체이기 때문에 자신만의 고유의 속성과 메소드가 있습니다. 그리고 이 속성과 메소드는 자신의 인스턴스들에게 상속되지 않습니다.

 

그렇기에 생성자에서 직접 접근할 수 있는 속성과 메소드들을 우리는 정적 메소드라고 합니다. 

Array.from('abc');//['a','b','c']

이렇게 array-like(배열 비스무리한 데이터)들을 배열로 만드는 Array.from()메소드를 예로 들수 있습니다.

 

정적 메소드의 특징은 어디에서 사용되든 값은 항상 같습니다. 즉 static 정적 이라는 의미입니다.

 

하지만 인스턴스 메소드들은 어떤 인스턴스의 값이 그 scope에서 무엇이냐에 따라 값이 달라집니다.  

 

 

Factory functions

 

factory 함수는 객체를 만드는 함수라는 점에서 생성자와 일치하지만 new 키워드를 사용하지 않는다. 그래서 return을 사용해 반환할 객체를 정의해야한다.

 

factory 함수는 생성자와 마찬가지로 객체를 만드는 역활을 하지만 단점이 확실한게 있다.

 

function Human(name,age){
   let human = {};
   human.name = name;
   human.age = age;
   
   return human;
}

let human1 = Human('Bob',12);

딱봐도 문제점이 보인다 이 코드는 new 키워드를 사용하지 않기 때문에 new키워드의 핵심 역활인 프로토타입의 상속이 불가하다. 만들어진 객체는 Human 생성자가 아닌 빌트인 Object 생성자의 프로토타입만 상속받을 것이다.

 

세번쪠 방법인 object.create는 다음에 계속 쓰곘습니다